diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0e39eeb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI/CD Pipeline + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with coverage + run: | + pytest --cov=./ --cov-report=xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage.xml \ No newline at end of file diff --git a/config.py b/config.py index f157a16..afa6b14 100644 --- a/config.py +++ b/config.py @@ -1,75 +1,90 @@ import os import sys -import yaml -from loguru import logger from typing import Optional +from pydantic_settings import BaseSettings +from pydantic import validator, ConfigDict +from loguru import logger +from watchfiles import watch, Change +from threading import Thread -# Define a custom exception for configuration errors -class AppConfigError(Exception): - pass +class LogConfig(BaseSettings): + level: str = 'INFO' + + model_config = ConfigDict( + env_prefix='LOG_', + extra='ignore' + ) + +class LLMConfig(BaseSettings): + mode: str = 'ollama' + + # OpenAI settings + openai_api_key: Optional[str] = None + openai_api_base_url: Optional[str] = None + openai_model: Optional[str] = None + + # Ollama settings + ollama_base_url: Optional[str] = None + ollama_model: Optional[str] = None + + @validator('mode') + def validate_mode(cls, v): + if v not in ['openai', 'ollama']: + raise ValueError("LLM mode must be either 'openai' or 'ollama'") + return v + + model_config = ConfigDict( + env_prefix='LLM_', + env_file='.env', + env_file_encoding='utf-8', + extra='ignore' + ) class Settings: - def __init__(self, config_path: str = "config/application.yml"): - """ - Loads configuration from a YAML file and overrides with environment variables. - """ - # --- Load from YAML file --- - try: - with open(config_path, 'r') as f: - config = yaml.safe_load(f) - except FileNotFoundError: - raise AppConfigError(f"Configuration file not found at '{config_path}'.") - except yaml.YAMLError as e: - raise AppConfigError(f"Error parsing YAML file: {e}") - - # --- Read and Combine Settings (Environment variables take precedence) --- - llm_config = config.get('llm', {}) - - # General settings - self.llm_mode: str = os.getenv("LLM_MODE", llm_config.get('mode', 'openai')).lower() - - # OpenAI settings - openai_config = llm_config.get('openai', {}) - self.openai_api_key: Optional[str] = os.getenv("OPENAI_API_KEY", openai_config.get('api_key')) - self.openai_api_base_url: Optional[str] = os.getenv("OPENAI_API_BASE_URL", openai_config.get('api_base_url')) - self.openai_model: Optional[str] = os.getenv("OPENAI_MODEL", openai_config.get('model')) - - # Ollama settings - ollama_config = llm_config.get('ollama', {}) - self.ollama_base_url: Optional[str] = os.getenv("OLLAMA_BASE_URL", ollama_config.get('base_url')) - self.ollama_model: Optional[str] = os.getenv("OLLAMA_MODEL", ollama_config.get('model')) - + def __init__(self): + self.log = LogConfig() + self.llm = LLMConfig() self._validate() + self._start_watcher() def _validate(self): - """ - Validates that required configuration variables are set. - """ - logger.info(f"LLM mode set to: '{self.llm_mode}'") + logger.info(f"LLM mode set to: '{self.llm.mode}'") + + if self.llm.mode == 'openai': + if not self.llm.openai_api_key: + raise ValueError("LLM mode is 'openai', but OPENAI_API_KEY is not set.") + if not self.llm.openai_api_base_url: + raise ValueError("LLM mode is 'openai', but OPENAI_API_BASE_URL is not set.") + if not self.llm.openai_model: + raise ValueError("LLM mode is 'openai', but OPENAI_MODEL is not set.") - if self.llm_mode == 'openai': - if not self.openai_api_key: - raise AppConfigError("LLM mode is 'openai', but OPENAI_API_KEY is not set.") - if not self.openai_api_base_url: - raise AppConfigError("LLM mode is 'openai', but OPENAI_API_BASE_URL is not set.") - if not self.openai_model: - raise AppConfigError("LLM mode is 'openai', but OPENAI_MODEL is not set.") - - elif self.llm_mode == 'ollama': - if not self.ollama_base_url: - raise AppConfigError("LLM mode is 'ollama', but OLLAMA_BASE_URL is not set.") - if not self.ollama_model: - raise AppConfigError("LLM mode is 'ollama', but OLLAMA_MODEL is not set.") - - else: - raise AppConfigError(f"Invalid LLM_MODE: '{self.llm_mode}'. Must be 'openai' or 'ollama'.") + elif self.llm.mode == 'ollama': + if not self.llm.ollama_base_url: + raise ValueError("LLM mode is 'ollama', but OLLAMA_BASE_URL is not set.") + if not self.llm.ollama_model: + raise ValueError("LLM mode is 'ollama', but OLLAMA_MODEL is not set.") logger.info("Configuration validated successfully.") + def _start_watcher(self): + def watch_config(): + for changes in watch('config/application.yml'): + for change in changes: + if change[0] == Change.modified: + logger.info("Configuration file modified, reloading settings...") + try: + self.llm = LLMConfig() + self._validate() + logger.info("Configuration reloaded successfully") + except Exception as e: + logger.error(f"Error reloading configuration: {e}") + + Thread(target=watch_config, daemon=True).start() + # Create a single, validated instance of the settings to be imported by other modules. try: settings = Settings() -except AppConfigError as e: +except ValueError as e: logger.error(f"FATAL: {e}") logger.error("Application shutting down due to configuration error.") - sys.exit(1) # Exit the application if configuration is invalid \ No newline at end of file + sys.exit(1) \ No newline at end of file diff --git a/config/application.yml b/config/application.yml index 1a63981..0544b7d 100644 --- a/config/application.yml +++ b/config/application.yml @@ -10,7 +10,7 @@ llm: # It's HIGHLY recommended to set this via an environment variable # instead of saving it in this file. # Can be overridden by OPENAI_API_KEY - api_key: "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" + api_key: "sk-or-v1-..." # Can be overridden by OPENAI_API_BASE_URL api_base_url: "https://openrouter.ai/api/v1" @@ -21,10 +21,11 @@ llm: # Settings for Ollama ollama: # Can be overridden by OLLAMA_BASE_URL - base_url: "http://localhost:11434" + base_url: "http://192.168.0.122:11434" + # base_url: "https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama" # Can be overridden by OLLAMA_MODEL - # model: "phi4-mini:latest" + model: "phi4-mini:latest" # model: "qwen3:1.7b" - model: "smollm:360m" + # model: "mollm:360m" \ No newline at end of file diff --git a/custom payload JIRA.json b/custom payload JIRA.json deleted file mode 100644 index 57791d7..0000000 --- a/custom payload JIRA.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "issueKey": "{{issue.key}}", - "summary": "{{issue.fields.summary}}", - "description": "{{issue.fields.description}}", - "comment": "{{comment.body}}", - "labels": "{{issue.fields.labels}}", - "status": "{{issue.fields.status.name}}", - "assignee": "{{issue.fields.assignee.displayName}}", - "updated": "{{issue.fields.updated}}" -} - - -RESPONSE - - -{ "issueKey": "HSM-1235", "summary": "RE: MDM Database Export", "description": "Hi Barbara, As you requested reports we have shared. So, we are closing this ticket. Regards Deepan *From:* Paulraj, Deepanraj *Sent:* Wednesday, July 24, 2024 2:16 PM *To:* Veberič, Barbara ; DL-Global MDM Support *Cc:* DL-ATP_MDMHUB_SUPPORT_PROD *Subject:* RE: MDM Database Export Hi Barbara, Please find the attached file. Regards Deepan *From:* Paulraj, Deepanraj *Sent:* Tuesday, July 23, 2024 6:36 PM *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; DL-Global MDM Support <[DL-Global-MDM-Support@pfizer.com|mailto:DL-Global-MDM-Support@pfizer.com]> *Cc:* DL-ATP_MDMHUB_SUPPORT_PROD <[DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com|mailto:DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com]> *Subject:* RE: MDM Database Export Hi [@Veberič, Barbara|mailto:Barbara.Veberic@pfizer.com], Below request is acknowledge with IM ticket no: *IM44430883* Currently, we have started to export these reports, once completed we will share you. Regards Deepan *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> *Sent:* Tuesday, July 23, 2024 4:26 PM *To:* DL-ATP_MDMHUB_SUPPORT_PROD <[DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com|mailto:DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com]>; DL-Global MDM Support <[DL-Global-MDM-Support@pfizer.com|mailto:DL-Global-MDM-Support@pfizer.com]> *Subject:* FW: MDM Database Export Dear Colleagues, I was referred to you by [@MANTRI, KOMAL|mailto:KOMAL.MANTRI@pfizer.com]. I'm interested in obtaining an export via MDM that includes *approved speakers per country and their last briefing date.* Specifically, I require data for *all Adriatic countries,* including Slovenia, Croatia, Bosnia, and Serbia. Thank you in advance! Best, Barbara *{color:#0000C9} *Barbara Veberič, MPharm*{color}* {color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} {color:#0000C9}{color} *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} {color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} {color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} *{color:#0000C7} {color}* *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ *From:* MANTRI, KOMAL <[KOMAL.MANTRI@pfizer.com|mailto:KOMAL.MANTRI@pfizer.com]> *Sent:* Tuesday, July 23, 2024 12:50 PM *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> *Subject:* RE: MDM Database Export Hi [@Veberič, Barbara|mailto:Barbara.Veberic@pfizer.com], Thank you for reaching out. However, the below markets are currently not available in Pfizer Reltio. Please reach out to HUB and IQVIA Reltio team for exports. [DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com/|mailto:DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com/] [DL-Global-MDM-Support@pfizer.com|mailto:DL-Global-MDM-Support@pfizer.com] Regards, Komal *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> *Sent:* Tuesday, July 23, 2024 4:02 PM *To:* MANTRI, KOMAL <[KOMAL.MANTRI@pfizer.com|mailto:KOMAL.MANTRI@pfizer.com]> *Subject:* MDM Database Export Dear Komal, I was referred to you by [@Kesisoglou, Eleni|mailto:Eleni.Kesisoglou@pfizer.com]. I'm interested in obtaining an export via MDM that includes *approved speakers per country and their last briefing date.* Specifically, I require data for *all Adriatic countries,* including Slovenia, Croatia, Bosnia, and Serbia. Thank you in advance! Best, Barbara *{color:#0000C9} *Barbara Veberič, MPharm*{color}* {color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} {color:#0000C9}{color} *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} {color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} {color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} *{color:#0000C7} {color}* *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ *From:* Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]> *Sent:* Tuesday, July 23, 2024 11:53 AM *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> *Cc:* Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Hello dear Barbara, Since Mariem is on leave, I would like to inform you that we discussed your query along with our global team in our previous catch up and we concluded that MAPP Navigator can currently not provide any export that would contain approved speakers with expiration/renewal date. This export although could be made via MDM database. Kindly refer to [komal.mantri@pfizer.com|mailto:komal.mantri@pfizer.com] who has provided to us such an export for AfME two months ago. Even there although, despite the fact that we may see if an HCP is approved, we can see only his/her last briefing date and not the expiration date: !image001.png|thumbnail! In any case, I believe an extract like this for now would be a good start for your market so as to begin maintaining an approved speaker list till any future enhancement in MAPP Navigator reporting/exporting system. Best Regards, !image002.png|thumbnail!!image003.png|thumbnail! *From**:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> *Sent**:* Tuesday, July 23, 2024 12:34 PM *To**:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]> *Cc**:* Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject**:* RE: Update to Global Speaker Program Policy Hi, I wanted to follow-up on my previous email regarding Section 2.3 – can we generate a country-specific list of approved speakers from the MAPP Navigator system (e.g. export Excel)? Is it possible to view the approval date of each speaker and their expiration date for renewal within the MAPP Navigator system? Thanks in advance for your feedback! Best, Barbara *{color:#0000C9} *Barbara Veberič, MPharm*{color}* {color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} {color:#0000C9}{color} *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} {color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} {color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} *{color:#0000C7} {color}* *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ *From:* Veberič, Barbara *Sent:* Wednesday, July 17, 2024 11:38 AM *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Hi, Very helpful. Thank you! Regarding Section 2.3 (Speakers – Approved List and Periodic Checks) – can we generate a country-specific list of approved speakers from the MAPP Navigator system (e.g. export Excel)? Is it possible to view the approval date of each speaker and their expiration date for renewal within the MAPP Navigator system? Best, Barbara *{color:#0000C9} *Barbara Veberič, MPharm*{color}* {color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} {color:#0000C9}{color} *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} {color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} {color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} *{color:#0000C7} {color}* *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ *From:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> *Sent:* Wednesday, July 17, 2024 11:28 AM *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Hi Barbara. Sorry I missed your email please see below my answers below in green. Let me please if you need any further details Best Regards Mariem *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> *Sent:* Thursday, July 11, 2024 1:23 PM *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Hi, Thank you once again! I still need some clarification on policy sections 2.3 and 2.5 – please see below. Regarding Section 2.3 (Speakers – Approved List and Periodic Checks), I understand that our local Medical Affairs colleagues will maintain the updated list of qualified and approved speakers. However, I would like to know *who can assist our market in setting up a speaker lis**t.* {color:#70AD47}Not sure I I got well this time your question{color} {color:#70AD47}😊{color}{color:#70AD47} The approved speaker list is maintained in MDM (Navigator HCP database). As long as there are historical transactions with speakers approved, they will be automatically saved in MDM and will be expired after 1 year. You do not need to separately maintain a speaker list.{color} Additionally, concerning Section 2.5 (Speaker Materials and Content), the policy states that markets using the MAPP Navigator system should upload approved speaker program content and its respective medical approval into the system. My question is *whether MAPP Navigator can serve as the sole archive for speaker program content, or if we need to maintain a separate local archive in a centralized local repository.:* {color:#70AD47}Agreed MN can be a central repository for external created content and its approval. However, for internal core deck, it still needs to go through GCMA and it is governed by medical team{color} Best, Barbara *{color:#0000C9} *Barbara Veberič, MPharm*{color}* {color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} {color:#0000C9}{color} *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} {color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} {color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} *{color:#0000C7} {color}* *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ *From:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> *Sent:* Thursday, July 11, 2024 11:41 AM *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Dear Barbara I am very pleased to assist ! Kindly note below my comments in Green & please don’t hesitate if you have further questions & clarifications. Best Regards | *{color:black} *Mariem Ben Abdallah*{color}*{color:black} EMEA Speaker Program & Policy operations Senior Manager{color} | | | | *{color:#0000C9} *Meetings, External Engagements & Travel (MEET)*{color}*{color:black} Your Journey, Our Priority!{color} | *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> *Sent:* Wednesday, July 10, 2024 9:20 AM *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Dear Mariem, Thank you for your feedback. I still have a few remaining questions—apologies for the back-and-forth. * Is Version #3 of the GSPP, last updated in June 2024, the final version of the policy that will be effective from September 15, 2024? {color:#70AD47}YES effective date for all update will be SEP 15th{color} * Could we arrange local training sessions for relevant colleagues on the updated GSPP before its effective date, for example in early September? {color:#70AD47}MEET regional speaker program team will set up meetings to go through the policy updates and operational requirement with market stakeholders in next few weeks. In between Please feel free to contact myself and Eleni cc’d above if you have any other questions before the meeting. Also Marjeta in case there is any insight from local compliance perspective.{color} Additionally, I have specific questions regarding the policy sections: * Section 2.3 Speakers – Approved List and Periodic Checks: Who can assist us in establishing and maintaining an approved list of speakers? {color:#70AD47}Medical Affairs colleague will be suuporting on that please refer to section 3 in Roles & Responsibilities as well in the policy as per the below screen shot{color} !image004.jpg|thumbnail! * {color:#70AD47} {color}{color:windowtext}Section 2.5 Speaker Materials and Content – Retention: Can we exclusively utilize the MAPP Navigator system for storing final versions of speaker program presentations and their corresponding medical approval emails? This would allow us to remove archives from our centralized local repositories as per the June 2024 version of the GSPP, which requires final, approved Speaker Program content to be uploaded into MAPP Navigator post-delivery, along with evidence of review and approval.{color} {color:#70AD47}This is the expectation from the policy : for Markets Using Mapp Navigator final, approved Speaker Program content must be uploaded in{color} Thanks again! Best, Barbara *{color:#0000C9} *Barbara Veberič, MPharm*{color}* {color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} {color:#0000C9}{color} *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} {color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} {color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} *{color:#0000C7} {color}* *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ *From:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> *Sent:* Tuesday, July 9, 2024 3:20 PM *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Dear Barbara. Kindly see below my comments Let me know please if this clear for you. Regards Mariem *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> *Sent:* Tuesday, July 9, 2024 8:17 AM *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Hi, Thank you for clarifying. I also have a few additional questions: * Who can assist us with updating the local SOP if we choose to maintain it as a reference? {color:#2B9B62}as stated, the expectation is to remove the local SOP from Markets , All local requirements should have been transitioned to the Country Annex Portal ( CAP ) and any future updates should be made therein to respond to your question if you prefer keep it the Local CCL will be responsible for updating it in alignment with the global Policy : Local SOP should be as consistent as the CAP{color} * Will training on the updated version of the GSPP be available before its effective date? {color:#2B9B62}Global SPP Training including updates will be launched by the End of this year.{color} Can we obtain core slides for the updated GSPP to facilitate local-level training sessions? {color:#2B9B62}Please refer to the PPT attached in Global the communication - I am attaching also for your reference a different version of comparisons between the old policy language and the new language. It’s not every tiny change but includes the main updates that we made in the latest version. It’s not perfectly cleaned up, but hopefully will do the trick for now.{color} Best, Barbara *{color:#0000C9} *Barbara Veberič, MPharm*{color}* {color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} {color:#0000C9}{color} *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} {color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} {color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} *{color:#0000C7} {color}* *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ *From:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> *Sent:* Monday, July 8, 2024 11:56 AM *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* RE: Update to Global Speaker Program Policy Dear Barbara. Thank you for reaching out to us. Indeed, the update will be effective by September 15th, 2024, these updates should be reflected into the CAP of each Market ( Speaker Program Management section) ; if you may see in the update the expectation is to remove the local SOP & All local requirements should have been transitioned to the Country Annex Portal ( CAP ) and any future updates should be made therein unless the Market has a business reason to keep it as a reference hence in this case both local SOP & CAP must be accurate & consistent . We as Speaker Program Team are the owner of this policy and certainly will liase with you & [@Bojic Kek, Marjeta|mailto:Marjeta.BojicKek@pfizer.com] to make this update consistent & clear across the Market . ( you can refer to the section Roles & Responsibilities in Page 15 -17 Hope this clarifies your query , otherwise please let me know if you have further question happy to assist Best Regards | *{color:black} *Mariem Ben Abdallah*{color}*{color:black} EMEA Speaker Program & Policy operations Senior Manager{color} | | | | *{color:#0000C9} *Meetings, External Engagements & Travel (MEET)*{color}*{color:black} Your Journey, Our Priority!{color} | *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> *Sent:* Friday, July 5, 2024 11:46 AM *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]> *Cc:* Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]> *Subject:* FW: Update to Global Speaker Program Policy Dear Colleagues, My name is Barbara Veberič, and I have recently assumed the role of Medical Quality Governance Manager for the Adriatic region, succeeding Andrej Babnik, who will retire on July 15th. Andrej currently oversees the local Speaker Program Policy SOP for the Adriatic region and its MAPP Country Annexes for Slovenia, Croatia, Serbia, Bosnia, and CBC. As I take on this responsibility, I will serve as the interim owner until the updated version comes into effect. According to recent communications, there is a planned transfer of ownership for GSPP and an update to the policy, scheduled to take effect on September 15th, 2024. *I am seeking clarification on how these changes will impact our markets, particularly regarding the transfer of ownership, updates to local SOPs aligned with global policies, and revisions to local country annexes.* Your guidance during this transition period would be greatly appreciated, and I kindly request to be kept informed of any developments moving forward. Thanks in advance! Best, Barbara *{color:#0000C9} *Barbara Veberič, MPharm*{color}* {color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} {color:#0000C9}{color} *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} {color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} {color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} *{color:#0000C7} {color}* *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ *From:* Global Speaker Program and Policy <[GlobalSpeakerProgramandPolicy@pfizer.com|mailto:GlobalSpeakerProgramandPolicy@pfizer.com]> *Sent:* Wednesday, 3 July 2024 21:14 *To:* No reply <[noreply-OnMessage@pfizer.com|mailto:noreply-OnMessage@pfizer.com]> *Subject:* Update to Global Speaker Program Policy Dear Colleagues. We hope all is well at your end. As you may be aware, over the past few months, the Compliance team, and the Global Business Services - Meetings, External Engagements, and Travel - Process Integration & Controls team (GBS MEET PI&C) have been working to transition the ownership of the Global Speaker Program Policy, which was previously managed by the Global Risk Lead within Compliance. In parallel, we received endorsement from the Commercial Quality & Risk Committee (CQRC) to build a team within the GBS MEET PI&C organization to support operationalization of speaker program requirements within markets across {color:black} the respective regions (EMEA, LATAM, and APAC).{color} The objective of this new team is to further streamline operations across markets while enhancing overall efficiency and strengthening risk management. We look forward to receiving your continued attention and support during this crucial period of transition as the team assumes their new responsibilities. Below is an overview of the GBS MEET PI&C team that will support the Global Policy and Process Oversight:{color:black}{color} | *Global Contacts* | ? Brian Badal (Sr. Director, Global MEET PI&C Lead) ? Huzefa Rangwala (Director, MEET PI&C and Global Speaker Program Policy Owner) | | *Regional Contacts* | *APAC :* * Katherine Yang (Sr. Manager - MEET PI&C APAC Regional Lead)* Li Peng Lin (Sr. Analyst - MEET PI&C APAC Region) | | *EMEA (excluding Spain and Portugal):* * Mariem Ben Abdallah (Sr. Manager - MEET PI&C EMEA Regional Lead) * Eleni Kesisoglou (Sr. Analyst - EMEA Region) | | *LATAM : and including Canada, Spain, and Portugal* * Andrea Villalobos (Director - MEET PI&C) * Norman Leandro (Sr. Manager - MEET LATAM Regional Lead) * Alejandra Perez (Sr. Analyst - LATAM Region) | Alison Page (Global Risk Lead for HCP Consultancies) will continue to partner and support the GBS MEET PI&C team in managing the Global Speaker Program Policy going forward. We have made updates to the Global Speaker Program Policy to incorporate updated roles & responsibilities of various functions associated with speaker programs, including the new responsibilities for GBS MEET PI&C, as well as to clarify certain policy requirements. The updated policy will become effective on *{color:#00B050} *September 15, 2024.*{color}* The updated policy and a summary of the recent updates are attached to this email. Please cascade this message to your team and other relevant colleagues, as needed. In the coming days, GBS MEET PI&C Regional teams will be reaching out to colleagues in the Markets to support the transition and will partner with market Compliance to update the requirements within the local Country Annex, as needed. Your cooperation and commitment to adapting these changes are greatly appreciated. If you have any questions or concerns, please feel free to reach out to the above mentioned contact points or to our mailbox at [GlobalSpeakerProgramandPolicy@pfizer.com|mailto:GlobalSpeakerProgramandPolicy@pfizer.com]. *Best regards,* *Brian Badal, GBS MEET PI&C Lead & Alison Page, Global Risk Lead*", "comment": "Thank you for the update. !image001.png|thumbnail! !image002.png|thumbnail!!image003.png|thumbnail! !image004.jpg|thumbnail!, mdm hub, please help me, MDM HUB please help me respond, MDM HUB can you respond please, MDM team, we need a status update", "labels": "mdm_escalation", "status": "Canceled", "assignee": "Anuskiewicz, Piotr", "updated": "2025-07-04T05:28:24.4-0400" } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index aff0bb5..7bb3e97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,65 +1,33 @@ -name: jira-llm-stack +name: jira-webhook-stack services: - # Service for the Ollama server - ollama: - image: ollama/ollama:latest - # Map port 11434 from the container to the host machine - # This allows you to access Ollama directly from your host if needed (e.g., via curl http://localhost:11434) + ollama-jira: + image: artifactory.pfizer.com/mdmhub-docker-dev/mdmtools/ollama/ollama-preloaded:0.0.1 ports: - "11434:11434" - # Mount a volume to persist Ollama models and data - # This prevents redownloading models every time the container restarts - volumes: - - ollama_data:/root/.ollama - - # CORRECTED COMMAND: - # We explicitly tell Docker to use 'bash -c' to execute the string. - # This ensures that 'ollama pull' and 'ollama serve' are run sequentially. - entrypoint: ["sh"] - command: ["-c", "ollama serve && ollama pull phi4-mini:latest"] - - # Restart the container if it exits unexpectedly restart: unless-stopped # Service for your FastAPI application - app: - # Build the Docker image for your app from the current directory (where Dockerfile is located) - build: . - # Map port 8000 from the container to the host machine - # This allows you to access your FastAPI app at http://localhost:8000 + jira-webhook-llm: + image: artifactory.pfizer.com/mdmhub-docker-dev/mdmtools/ollama/jira-webhook-llm:0.1.8 ports: - "8000:8000" - # Define environment variables for your FastAPI application - # These will be read by pydantic-settings in your app environment: - # Set the LLM mode to 'ollama' + # Set the LLM mode to 'ollama' or 'openai' LLM_MODE: ollama + # Point to the Ollama service within the Docker Compose network # 'ollama' is the service name, which acts as a hostname within the network - OLLAMA_BASE_URL: http://192.168.0.122:11434 + OLLAMA_BASE_URL: "https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama" + # Specify the model to use - OLLAMA_MODEL: gemma3:1b - # If you have an OpenAI API key in your settings, but want to ensure it's not used - # when LLM_MODE is ollama, you can explicitly set it to empty or omit it. - # OPENAI_API_KEY: "" - # OPENAI_MODEL: "" + OLLAMA_MODEL: phi4-mini:latest + # Ensure the Ollama service starts and is healthy before starting the app depends_on: - - ollama - # Restart the container if it exits unexpectedly + - ollama-jira restart: unless-stopped - # Mount your current project directory into the container - # This is useful for development, as changes to your code will be reflected - # without rebuilding the image (if you're using a hot-reloading server like uvicorn --reload) - # For production, you might remove this and rely solely on the Dockerfile copy. - volumes: - - .:/app + # Command to run your FastAPI application using Uvicorn # --host 0.0.0.0 is crucial for the app to be accessible from outside the container # --reload is good for development; remove for production - command: uvicorn jira-webhook-llm:app --host 0.0.0.0 --port 8000 --reload - -# Define named volumes for persistent data -volumes: - ollama_data: - driver: local \ No newline at end of file + command: uvicorn jira-webhook-llm:app --host 0.0.0.0 --port 8000 \ No newline at end of file diff --git a/full JIRA payload.json b/full JIRA payload.json deleted file mode 100644 index 727a043..0000000 --- a/full JIRA payload.json +++ /dev/null @@ -1,1056 +0,0 @@ -{ - "self": "https://jira.pfizer.com/rest/api/2/issue/2359796", - "id": 2359796, - "key": "HSM-1235", - "changelog": { - "startAt": 0, - "maxResults": 0, - "total": 0, - "histories": null - }, - "fields": { - "customfield_19200": null, - "customfield_19201": null, - "customfield_23701": null, - "customfield_16173": "10904_*:*_1_*:*_0_*|*_11100_*:*_1_*:*_11024926", - "customfield_19202": null, - "customfield_16172": null, - "customfield_24913": "3 business days ", - "customfield_19203": null, - "customfield_16171": null, - "customfield_23704": null, - "customfield_19680": null, - "customfield_19204": null, - "customfield_31203": null, - "resolution": { - "self": "https://jira.pfizer.com/rest/api/2/resolution/10502", - "id": 10502, - "name": "Cancelled", - "description": "This is not an issue any longer", - "namedValue": "Cancelled" - }, - "customfield_31202": null, - "customfield_31205": null, - "customfield_12800": null, - "customfield_10501": null, - "customfield_10502": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/10801", - "value": "No", - "id": "10801", - "disabled": false - }, - "customfield_10505": null, - "customfield_10506": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/10805", - "value": "One-off", - "id": "10805", - "disabled": false - }, - "customfield_16170": null, - "customfield_16163": "None ", - "customfield_19674": null, - "customfield_23833": null, - "lastViewed": "2025-07-04T05:28:26.154-0400", - "customfield_16161": "None", - "customfield_16167": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20185", - "value": "RNAseq", - "id": "20185", - "disabled": false - }, - "customfield_16166": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20177", - "value": "RNAseq", - "id": "20177", - "disabled": false - }, - "customfield_19679": null, - "customfield_11700": "{summaryBean=com.atlassian.jira.plugin.devstatus.rest.SummaryBean@7d20d28f[summary={pullrequest=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@4b9c31d9[overall=PullRequestOverallBean{stateCount=0, state='OPEN', details=PullRequestOverallDetails{openCount=0, mergedCount=0, declinedCount=0}},byInstanceType={}], build=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@5ccdf943[overall=com.atlassian.jira.plugin.devstatus.summary.beans.BuildOverallBean@767519fa[failedBuildCount=0,successfulBuildCount=0,unknownBuildCount=0,count=0,lastUpdated=,lastUpdatedTimestamp=],byInstanceType={}], review=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@4c6def8f[overall=com.atlassian.jira.plugin.devstatus.summary.beans.ReviewsOverallBean@7c433b57[stateCount=0,state=,dueDate=,overDue=false,count=0,lastUpdated=,lastUpdatedTimestamp=],byInstanceType={}], deployment-environment=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@4f77c27d[overall=com.atlassian.jira.plugin.devstatus.summary.beans.DeploymentOverallBean@1193a8a0[topEnvironments=[],showProjects=false,successfulCount=0,count=0,lastUpdated=,lastUpdatedTimestamp=],byInstanceType={}], repository=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@32213c8a[overall=com.atlassian.jira.plugin.devstatus.summary.beans.CommitOverallBean@575d201a[count=0,lastUpdated=,lastUpdatedTimestamp=],byInstanceType={}], branch=com.atlassian.jira.plugin.devstatus.rest.SummaryItemBean@264874a6[overall=com.atlassian.jira.plugin.devstatus.summary.beans.BranchOverallBean@2a921e67[count=0,lastUpdated=,lastUpdatedTimestamp=],byInstanceType={}]},errors=[],configErrors=[]], devSummaryJson={\"cachedValue\":{\"errors\":[],\"configErrors\":[],\"summary\":{\"pullrequest\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":\"OPEN\",\"details\":{\"openCount\":0,\"mergedCount\":0,\"declinedCount\":0,\"total\":0},\"open\":true},\"byInstanceType\":{}},\"build\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"failedBuildCount\":0,\"successfulBuildCount\":0,\"unknownBuildCount\":0},\"byInstanceType\":{}},\"review\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"stateCount\":0,\"state\":null,\"dueDate\":null,\"overDue\":false,\"completed\":false},\"byInstanceType\":{}},\"deployment-environment\":{\"overall\":{\"count\":0,\"lastUpdated\":null,\"topEnvironments\":[],\"showProjects\":false,\"successfulCount\":0},\"byInstanceType\":{}},\"repository\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}},\"branch\":{\"overall\":{\"count\":0,\"lastUpdated\":null},\"byInstanceType\":{}}}},\"isStale\":false}}", - "customfield_30013": null, - "aggregatetimeoriginalestimate": null, - "issuelinks": [], - "assignee": { - "self": "https://jira.pfizer.com/rest/api/2/user?username=ANUSKP", - "name": "ANUSKP", - "key": "JIRAUSER32000", - "accountId": null, - "emailAddress": "Piotr.Anuskiewicz@pfizer.com", - "avatarUrls": { - "48x48": "https://jira.pfizer.com/secure/useravatar?avatarId=10352", - "24x24": "https://jira.pfizer.com/secure/useravatar?size=small&avatarId=10352", - "16x16": "https://jira.pfizer.com/secure/useravatar?size=xsmall&avatarId=10352", - "32x32": "https://jira.pfizer.com/secure/useravatar?size=medium&avatarId=10352" - }, - "displayName": "Anuskiewicz, Piotr", - "active": true, - "timeZone": "Europe/Warsaw", - "groups": null, - "locale": null - }, - "customfield_19662": "None", - "customfield_19300": null, - "customfield_16151": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20141", - "value": "No", - "id": "20141", - "disabled": false - }, - "customfield_19540": null, - "customfield_19661": "For Database:\n\nServer: \n __________________________________________\n\nDatabase name: \n________________________________\n\nAccount to be used: \n____________________________\n", - "customfield_17000": null, - "customfield_19549": null, - "customfield_16158": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20148", - "value": "No", - "id": "20148", - "disabled": false - }, - "customfield_19651": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/37002", - "value": "Support", - "id": "37002", - "disabled": false - }, - "customfield_18562": null, - "customfield_16140": null, - "customfield_17350": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27314", - "value": "None", - "id": "27314", - "disabled": true - }, - "customfield_16145": "", - "subtasks": [], - "customfield_16143": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20138", - "value": "No", - "id": "20138", - "disabled": false - }, - "customfield_16149": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20134", - "value": "Standard (all work can continue)", - "id": "20134", - "disabled": false - }, - "customfield_19659": [ - { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36963", - "value": "Global", - "id": "36963", - "disabled": false - } - ], - "customfield_18208": null, - "customfield_16148": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20147", - "value": "No", - "id": "20147", - "disabled": false - }, - "customfield_16147": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20135", - "value": "Boulder, CO - US", - "id": "20135", - "disabled": false - }, - "customfield_16146": null, - "customfield_31115": null, - "customfield_19655": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36738", - "value": "New", - "id": "36738", - "disabled": false - }, - "customfield_19657": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36764", - "value": "No", - "id": "36764", - "disabled": false - }, - "customfield_19658": 0, - "customfield_11800": [ - "Portal-Request" - ], - "votes": { - "self": "https://jira.pfizer.com/rest/api/2/issue/HSM-1235/votes", - "votes": 0, - "hasVoted": false - }, - "customfield_21603": "-1", - "customfield_21602": "com.riadalabs.jira.plugins.notificationassistant.model.dtos.RecipientsDTO@4310f711", - "customfield_25921": 0, - "customfield_18551": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27181", - "value": "EST", - "id": "27181", - "disabled": false - }, - "issuetype": { - "self": "https://jira.pfizer.com/rest/api/2/issuetype/10801", - "id": 10801, - "description": "This will be used to make any configuration changes in the cluster", - "iconUrl": "https://jira.pfizer.com/secure/viewavatar?size=xsmall&avatarId=12252&avatarType=issuetype", - "name": "Service Request", - "subtask": false, - "fields": null, - "statuses": [], - "namedValue": "Service Request" - }, - "customfield_19641": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36279", - "value": "Yes", - "id": "36279", - "disabled": false - }, - "customfield_18553": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27186", - "value": "All", - "id": "27186", - "disabled": false - }, - "customfield_25924": null, - "customfield_19642": "Below Portfolio Threshold", - "customfield_19643": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/39842", - "value": "01 - New", - "id": "39842", - "disabled": false - }, - "customfield_16134": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20101", - "value": "No", - "id": "20101", - "disabled": false - }, - "customfield_18559": null, - "customfield_16138": null, - "customfield_16137": null, - "customfield_18555": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27325", - "value": "BOBJ", - "id": "27325", - "disabled": false - }, - "customfield_19645": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36727", - "value": "False", - "id": "36727", - "disabled": false - }, - "customfield_18558": "10904_*:*_1_*:*_0_*|*_11100_*:*_1_*:*_11024926", - "customfield_16139": null, - "customfield_19647": null, - "customfield_19750": null, - "customfield_26902": "https://pfizer.sharepoint.com/sites/BTSMBAU/_layouts/15/Doc.aspx?sourcedoc=%7B46731090-a96e-4a0e-ad31-58dfdf05605f%7D&action=edit&wd=target%28Agile%2FDoD.one%7C770e1333-f30e-4c6c-971e-3a9ab97ad6c0%2F%29&wdorigin=717", - "customfield_18662": null, - "customfield_19630": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36626", - "value": "No", - "id": "36626", - "disabled": false - }, - "customfield_18663": null, - "customfield_18664": null, - "customfield_18548": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27339", - "value": "Operational", - "id": "27339", - "disabled": false - }, - "customfield_19637": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36729", - "value": "TBD", - "id": "36729", - "disabled": false - }, - "customfield_19638": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/38667", - "value": "Please Select From Drop Down", - "id": "38667", - "disabled": false - }, - "customfield_19639": "2021-07-01", - "customfield_18544": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27332", - "value": "0", - "id": "27332", - "disabled": false - }, - "customfield_19633": "\r\n•\t< 40 cols ____________________________\r\n\r\n•\t40 – 100 cols _________________________\r\n\r\n•\t100 – 400 cols _________________________\r\n\r\n\r\n•\t> 400 cols ____________________________\r\n", - "customfield_16009": null, - "customfield_18545": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27252", - "value": "0", - "id": "27252", - "disabled": false - }, - "customfield_19634": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/37021", - "value": "TBD", - "id": "37021", - "disabled": false - }, - "customfield_16008": null, - "customfield_11900": null, - "customfield_23400": null, - "customfield_16350": null, - "customfield_18530": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27313", - "value": "0", - "id": "27313", - "disabled": false - }, - "customfield_19500": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/35701", - "value": "EXTERNAL", - "id": "35701", - "disabled": false - }, - "customfield_19742": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/42835", - "value": " ", - "id": "42835", - "disabled": true - }, - "customfield_17200": null, - "customfield_19626": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36672", - "value": "False", - "id": "36672", - "disabled": false - }, - "customfield_18534": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27227", - "value": "Full Load", - "id": "27227", - "disabled": false - }, - "customfield_19623": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36423", - "value": "Not Reviewed", - "id": "36423", - "disabled": false - }, - "customfield_19624": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/36810", - "value": "True", - "id": "36810", - "disabled": false - }, - "customfield_19503": null, - "timetracking": { - "originalEstimate": null, - "remainingEstimate": null, - "timeSpent": null, - "originalEstimateSeconds": 0, - "remainingEstimateSeconds": 0, - "timeSpentSeconds": 0 - }, - "customfield_32000": null, - "customfield_22900": null, - "customfield_18521": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27271", - "value": "China", - "id": "27271", - "disabled": false - }, - "customfield_26804": "https://pfizer.sharepoint.com/sites/BTSMBAU/_layouts/15/Doc.aspx?sourcedoc=%7B46731090-a96e-4a0e-ad31-58dfdf05605f%7D&action=edit&wd=target%28Agile%2FDoR.one%7Ccb27889e-7c10-4871-bf85-338dbd8177e7%2F%29&wdorigin=717", - "customfield_16101": null, - "customfield_24508": null, - "customfield_16100": null, - "customfield_18526": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/25449", - "value": "UDH", - "id": "25449", - "disabled": false - }, - "customfield_16105": null, - "customfield_18527": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27277", - "value": "USA", - "id": "27277", - "disabled": false - }, - "customfield_16346": null, - "customfield_16104": null, - "customfield_19739": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/72200", - "value": "Unknown", - "id": "72200", - "disabled": false - }, - "customfield_16102": null, - "customfield_18523": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27187", - "value": "Finance UDH", - "id": "27187", - "disabled": false - }, - "customfield_16107": null, - "customfield_16349": null, - "customfield_18525": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27183", - "value": "Prod", - "id": "27183", - "disabled": false - }, - "environment": null, - "customfield_19729": null, - "customfield_20716": null, - "duedate": null, - "customfield_20715": null, - "customfield_11320": null, - "customfield_11321": null, - "customfield_11322": null, - "customfield_11201": null, - "customfield_11323": null, - "customfield_11202": null, - "customfield_11203": null, - "customfield_11314": null, - "customfield_14704": null, - "customfield_11315": null, - "customfield_14705": null, - "customfield_11316": null, - "customfield_11317": null, - "customfield_12529": null, - "customfield_11318": null, - "customfield_11319": null, - "customfield_14709": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/17719", - "value": "Rare Diseases", - "id": "17719", - "disabled": false - }, - "customfield_30649": null, - "customfield_30646": null, - "customfield_30647": null, - "customfield_30648": null, - "customfield_14700": null, - "customfield_11310": null, - "customfield_11311": null, - "customfield_11312": null, - "customfield_11313": null, - "customfield_12514": null, - "customfield_15904": null, - "customfield_11304": null, - "customfield_30650": null, - "customfield_12515": null, - "customfield_30651": null, - "customfield_27000": null, - "customfield_11307": null, - "timeestimate": null, - "customfield_11308": null, - "customfield_27002": null, - "customfield_11309": null, - "customfield_27001": null, - "customfield_27003": null, - "status": { - "self": "https://jira.pfizer.com/rest/api/2/status/10904", - "description": "Change has been cancelled somewhere during the development process", - "iconUrl": "https://jira.pfizer.com/images/icons/statuses/generic.png", - "name": "Canceled", - "id": 10904, - "statusCategory": { - "self": "https://jira.pfizer.com/rest/api/2/statuscategory/3", - "id": 3, - "key": "done", - "colorName": "success", - "name": "Done" - } - }, - "customfield_30652": null, - "customfield_13601": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/15100", - "value": "Yes", - "id": "15100", - "disabled": false - }, - "archiveddate": null, - "customfield_13600": null, - "customfield_11302": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/11805", - "value": "GxP", - "id": "11805", - "disabled": false - }, - "aggregatetimeestimate": null, - "creator": { - "self": "https://jira.pfizer.com/rest/api/2/user?username=Deepanraj.Paulraj%40pfizer.com", - "name": "Deepanraj.Paulraj@pfizer.com", - "key": "JIRAUSER46885", - "accountId": null, - "emailAddress": "Deepanraj.Paulraj@pfizer.com", - "avatarUrls": { - "48x48": "https://www.gravatar.com/avatar/f768826e8465e098a65b7e00e869c67f?d=mm&s=48", - "24x24": "https://www.gravatar.com/avatar/f768826e8465e098a65b7e00e869c67f?d=mm&s=24", - "16x16": "https://www.gravatar.com/avatar/f768826e8465e098a65b7e00e869c67f?d=mm&s=16", - "32x32": "https://www.gravatar.com/avatar/f768826e8465e098a65b7e00e869c67f?d=mm&s=32" - }, - "displayName": "Deepanraj.Paulraj@pfizer.com", - "active": true, - "timeZone": "America/New_York", - "groups": null, - "locale": null - }, - "customfield_14800": null, - "customfield_13708": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/15212", - "value": "No", - "id": "15212", - "disabled": false - }, - "timespent": null, - "aggregatetimespent": null, - "customfield_10302": [ - "com.atlassian.greenhopper.service.sprint.Sprint@311053b4[id=56777,rapidViewId=2211,state=CLOSED,name=Sprint 118,startDate=2024-07-18T08:47:00.000-04:00,endDate=2024-07-31T08:47:00.000-04:00,completeDate=2024-08-01T09:19:24.750-04:00,activatedDate=2024-07-18T08:47:08.871-04:00,sequence=56777,goal=,synced=false,autoStartStop=false,incompleteIssuesDestinationId=]" - ], - "customfield_10303": "MR-8845", - "customfield_28001": null, - "workratio": -1, - "customfield_19117": null, - "customfield_19118": null, - "customfield_19119": null, - "customfield_29106": null, - "customfield_29107": null, - "customfield_29108": null, - "customfield_10401": null, - "customfield_11612": 100, - "customfield_11611": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/12127", - "value": "No", - "id": "12127", - "disabled": false - }, - "customfield_12702": null, - "customfield_11613": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/12129", - "value": "No", - "id": "12129", - "disabled": false - }, - "customfield_10405": null, - "customfield_22502": null, - "customfield_23818": null, - "customfield_11610": null, - "customfield_10511": null, - "customfield_11607": null, - "customfield_11606": null, - "customfield_11609": null, - "customfield_11608": null, - "customfield_27507": null, - "customfield_27509": null, - "customfield_27508": null, - "customfield_26419": "", - "customfield_12370": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/14700", - "value": "Undetermined", - "id": "14700", - "disabled": false - }, - "customfield_12490": null, - "customfield_16611": "None", - "customfield_12011": [], - "customfield_38400": null, - "customfield_12376": null, - "customfield_12012": null, - "customfield_12378": null, - "customfield_12377": null, - "customfield_12259": null, - "customfield_27879": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/73339", - "value": "No, hosted within Pfizer", - "id": "73339", - "disabled": false - }, - "customfield_29818": null, - "customfield_29819": null, - "customfield_29817": null, - "customfield_12361": null, - "customfield_12240": null, - "customfield_12484": null, - "customfield_18900": null, - "customfield_18901": null, - "customfield_12244": null, - "customfield_12488": null, - "customfield_12367": null, - "customfield_12003": null, - "customfield_12245": null, - "labels": [ - "mdm_escalation" - ], - "customfield_12239": null, - "customfield_27881": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/73343", - "value": "No", - "id": "73343", - "disabled": false - }, - "customfield_25100": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/68700", - "value": "0", - "id": "68700", - "disabled": false - }, - "components": [], - "customfield_12354": null, - "customfield_12233": null, - "customfield_12353": null, - "customfield_12232": null, - "customfield_12228": null, - "customfield_17914": null, - "customfield_12229": null, - "customfield_24020": null, - "customfield_24022": null, - "customfield_26322": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/71010", - "value": "No, this solution does not have wearable device compatibility", - "id": "71010", - "disabled": false - }, - "customfield_26324": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/71014", - "value": "No", - "id": "71014", - "disabled": false - }, - "customfield_26326": null, - "customfield_26328": null, - "customfield_12460": null, - "customfield_12462": null, - "customfield_39400": null, - "customfield_12582": null, - "customfield_39401": null, - "reporter": { - "self": "https://jira.pfizer.com/rest/api/2/user?username=Deepanraj.Paulraj%40pfizer.com", - "name": "Deepanraj.Paulraj@pfizer.com", - "key": "JIRAUSER46885", - "accountId": null, - "emailAddress": "Deepanraj.Paulraj@pfizer.com", - "avatarUrls": { - "48x48": "https://www.gravatar.com/avatar/f768826e8465e098a65b7e00e869c67f?d=mm&s=48", - "24x24": "https://www.gravatar.com/avatar/f768826e8465e098a65b7e00e869c67f?d=mm&s=24", - "16x16": "https://www.gravatar.com/avatar/f768826e8465e098a65b7e00e869c67f?d=mm&s=16", - "32x32": "https://www.gravatar.com/avatar/f768826e8465e098a65b7e00e869c67f?d=mm&s=32" - }, - "displayName": "Deepanraj.Paulraj@pfizer.com", - "active": true, - "timeZone": "America/New_York", - "groups": null, - "locale": null - }, - "customfield_39402": null, - "customfield_12584": "false", - "customfield_39403": null, - "customfield_12587": null, - "customfield_12466": null, - "customfield_12225": null, - "customfield_12217": null, - "customfield_12459": null, - "customfield_12579": null, - "customfield_12219": null, - "customfield_17904": null, - "customfield_25000": null, - "customfield_27421": null, - "customfield_25002": null, - "progress": { - "progress": 0, - "total": 0 - }, - "customfield_26337": null, - "customfield_27305": null, - "archivedby": null, - "customfield_26336": null, - "customfield_26339": null, - "customfield_27306": null, - "customfield_26338": null, - "customfield_12572": null, - "customfield_12451": null, - "customfield_31921": null, - "project": { - "self": "https://jira.pfizer.com/rest/api/2/project/36204", - "id": 36204, - "key": "HSM", - "name": "MDM HUB Service Management", - "description": null, - "avatarUrls": { - "48x48": "https://jira.pfizer.com/secure/projectavatar?pid=36204&avatarId=10201", - "24x24": "https://jira.pfizer.com/secure/projectavatar?size=small&pid=36204&avatarId=10201", - "16x16": "https://jira.pfizer.com/secure/projectavatar?size=xsmall&pid=36204&avatarId=10201", - "32x32": "https://jira.pfizer.com/secure/projectavatar?size=medium&pid=36204&avatarId=10201" - }, - "issuetypes": null, - "projectCategory": null, - "email": null, - "lead": null, - "components": null, - "versions": null, - "projectTypeKey": "service_desk", - "simplified": false - }, - "customfield_12332": null, - "customfield_12573": null, - "customfield_12578": null, - "customfield_12327": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/14709", - "value": "High - Cannot Be Recovered", - "id": "14709", - "disabled": false - }, - "customfield_12329": null, - "customfield_26340": null, - "resolutiondate": 1721995998410, - "customfield_25376": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/69959", - "value": "No", - "id": "69959", - "disabled": false - }, - "customfield_25378": null, - "watches": { - "self": "https://jira.pfizer.com/rest/api/2/issue/HSM-1235/watchers", - "watchCount": 1, - "isWatching": true - }, - "customfield_39301": null, - "customfield_39302": null, - "customfield_12200": null, - "customfield_16800": null, - "customfield_15710": "0", - "customfield_15711": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/19479", - "value": "A", - "id": "19479", - "disabled": false - }, - "customfield_11105": null, - "customfield_11106": null, - "customfield_11107": null, - "customfield_11108": { - "id": "834", - "name": "Time to first response", - "_links": { - "self": "https://jira.pfizer.com/rest/servicedeskapi/request/2359796/sla/834" - }, - "completedCycles": [ - { - "startTime": { - "iso8601": "2024-07-26T11:09:33+0200", - "jira": "2024-07-26T05:09:33.497-0400", - "friendly": "26/Jul/24 11:09 AM", - "epochMillis": 1721984973497 - }, - "stopTime": { - "iso8601": "2024-07-26T14:13:18+0200", - "jira": "2024-07-26T08:13:18.423-0400", - "friendly": "26/Jul/24 2:13 PM", - "epochMillis": 1721995998423 - }, - "breached": false, - "goalDuration": { - "millis": 57600000, - "friendly": "16h" - }, - "elapsedTime": { - "millis": 11024926, - "friendly": "3h 3m" - }, - "remainingTime": { - "millis": 46575074, - "friendly": "12h 56m" - } - } - ] - }, - "customfield_27200": null, - "customfield_15708": null, - "customfield_27202": null, - "customfield_27201": null, - "customfield_37000": [], - "customfield_29749": null, - "updated": 1751621304470, - "timeoriginalestimate": null, - "customfield_11340": null, - "description": "Hi Barbara, \n\n \n\nAs you requested reports we have shared. So, we are closing this ticket. \n\n \n\nRegards \n\nDeepan \n\n \n \n\n *From:* Paulraj, Deepanraj \n *Sent:* Wednesday, July 24, 2024 2:16 PM\n *To:* Veberič, Barbara ; DL-Global MDM Support \n *Cc:* DL-ATP_MDMHUB_SUPPORT_PROD \n *Subject:* RE: MDM Database Export \n\n \n\nHi Barbara, \n\n \n\nPlease find the attached file. \n\nRegards \n\nDeepan \n\n \n \n\n *From:* Paulraj, Deepanraj \n *Sent:* Tuesday, July 23, 2024 6:36 PM\n *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; DL-Global MDM Support <[DL-Global-MDM-Support@pfizer.com|mailto:DL-Global-MDM-Support@pfizer.com]>\n *Cc:* DL-ATP_MDMHUB_SUPPORT_PROD <[DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com|mailto:DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com]>\n *Subject:* RE: MDM Database Export \n\n \n\nHi [@Veberič, Barbara|mailto:Barbara.Veberic@pfizer.com], \n\n \n\nBelow request is acknowledge with IM ticket no: *IM44430883* \n\nCurrently, we have started to export these reports, once completed we will share you. \n\n \n\nRegards \n\nDeepan \n\n \n \n\n *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> \n *Sent:* Tuesday, July 23, 2024 4:26 PM\n *To:* DL-ATP_MDMHUB_SUPPORT_PROD <[DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com|mailto:DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com]>; DL-Global MDM Support <[DL-Global-MDM-Support@pfizer.com|mailto:DL-Global-MDM-Support@pfizer.com]>\n *Subject:* FW: MDM Database Export \n\n \n\nDear Colleagues, \n\nI was referred to you by [@MANTRI, KOMAL|mailto:KOMAL.MANTRI@pfizer.com]. I'm interested in obtaining an export via MDM that includes *approved speakers per country and their last briefing date.* Specifically, I require data for *all Adriatic countries,* including Slovenia, Croatia, Bosnia, and Serbia. \n\nThank you in advance! \n\nBest, \n\nBarbara \n\n *{color:#0000C9} *Barbara Veberič, MPharm*{color}* \n\n{color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} \n\n{color:#0000C9}{color} \n\n *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} \n\n{color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} \n\n{color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} \n\n *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} \n\n *{color:#0000C7} {color}* \n\n *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ \n\n \n \n\n *From:* MANTRI, KOMAL <[KOMAL.MANTRI@pfizer.com|mailto:KOMAL.MANTRI@pfizer.com]> \n *Sent:* Tuesday, July 23, 2024 12:50 PM\n *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>\n *Subject:* RE: MDM Database Export \n\n \n\nHi [@Veberič, Barbara|mailto:Barbara.Veberic@pfizer.com], \n\n \n\nThank you for reaching out. However, the below markets are currently not available in Pfizer Reltio. Please reach out to HUB and IQVIA Reltio team for exports. \n\n[DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com/|mailto:DL-ATP_MDMHUB_SUPPORT_PROD@pfizer.com/] [DL-Global-MDM-Support@pfizer.com|mailto:DL-Global-MDM-Support@pfizer.com] \n\n \n\nRegards, \n\nKomal \n \n\n *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> \n *Sent:* Tuesday, July 23, 2024 4:02 PM\n *To:* MANTRI, KOMAL <[KOMAL.MANTRI@pfizer.com|mailto:KOMAL.MANTRI@pfizer.com]>\n *Subject:* MDM Database Export \n\n \n\nDear Komal, \n\nI was referred to you by [@Kesisoglou, Eleni|mailto:Eleni.Kesisoglou@pfizer.com]. I'm interested in obtaining an export via MDM that includes *approved speakers per country and their last briefing date.* Specifically, I require data for *all Adriatic countries,* including Slovenia, Croatia, Bosnia, and Serbia. \n\nThank you in advance! \n\nBest, \n\nBarbara \n\n *{color:#0000C9} *Barbara Veberič, MPharm*{color}* \n\n{color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} \n\n{color:#0000C9}{color} \n\n *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} \n\n{color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} \n\n{color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} \n\n *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} \n\n *{color:#0000C7} {color}* \n\n *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ \n\n \n \n\n *From:* Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]> \n *Sent:* Tuesday, July 23, 2024 11:53 AM\n *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>\n *Cc:* Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nHello dear Barbara, \n\nSince Mariem is on leave, I would like to inform you that we discussed your query along with our global team in our previous catch up and we concluded that MAPP Navigator can currently not provide any export that would contain approved speakers with expiration/renewal date. This export although could be made via MDM database. Kindly refer to [komal.mantri@pfizer.com|mailto:komal.mantri@pfizer.com] who has provided to us such an export for AfME two months ago. Even there although, despite the fact that we may see if an HCP is approved, we can see only his/her last briefing date and not the expiration date: \n\n!image001.png|thumbnail! \n\nIn any case, I believe an extract like this for now would be a good start for your market so as to begin maintaining an approved speaker list till any future enhancement in MAPP Navigator reporting/exporting system. \n\n \n\nBest Regards, \n\n!image002.png|thumbnail!!image003.png|thumbnail! \n \n\n *From**:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> \n *Sent**:* Tuesday, July 23, 2024 12:34 PM\n *To**:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>\n *Cc**:* Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject**:* RE: Update to Global Speaker Program Policy \n\n \n\nHi, \n\nI wanted to follow-up on my previous email regarding Section 2.3 – can we generate a country-specific list of approved speakers from the MAPP Navigator system (e.g. export Excel)? Is it possible to view the approval date of each speaker and their expiration date for renewal within the MAPP Navigator system? \n\nThanks in advance for your feedback! \n\nBest, \n\nBarbara \n\n *{color:#0000C9} *Barbara Veberič, MPharm*{color}* \n\n{color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} \n\n{color:#0000C9}{color} \n\n *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} \n\n{color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} \n\n{color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} \n\n *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} \n\n *{color:#0000C7} {color}* \n\n *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ \n\n \n \n\n *From:* Veberič, Barbara \n *Sent:* Wednesday, July 17, 2024 11:38 AM\n *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nHi, \n\nVery helpful. Thank you! \n\nRegarding Section 2.3 (Speakers – Approved List and Periodic Checks) – can we generate a country-specific list of approved speakers from the MAPP Navigator system (e.g. export Excel)? Is it possible to view the approval date of each speaker and their expiration date for renewal within the MAPP Navigator system? \n\nBest, \n\nBarbara \n\n *{color:#0000C9} *Barbara Veberič, MPharm*{color}* \n\n{color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} \n\n{color:#0000C9}{color} \n\n *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} \n\n{color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} \n\n{color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} \n\n *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} \n\n *{color:#0000C7} {color}* \n\n *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ \n\n \n \n\n *From:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> \n *Sent:* Wednesday, July 17, 2024 11:28 AM\n *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nHi Barbara. \n\nSorry I missed your email please see below my answers below in green. \n\nLet me please if you need any further details \n\nBest Regards \n\nMariem \n\n \n \n\n *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> \n *Sent:* Thursday, July 11, 2024 1:23 PM\n *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nHi, \n\nThank you once again! I still need some clarification on policy sections 2.3 and 2.5 – please see below. \n\nRegarding Section 2.3 (Speakers – Approved List and Periodic Checks), I understand that our local Medical Affairs colleagues will maintain the updated list of qualified and approved speakers. However, I would like to know *who can assist our market in setting up a speaker lis**t.* {color:#70AD47}Not sure I I got well this time your question{color} {color:#70AD47}😊{color}{color:#70AD47} The approved speaker list is maintained in MDM (Navigator HCP database). As long as there are historical transactions with speakers approved, they will be automatically saved in MDM and will be expired after 1 year. You do not need to separately maintain a speaker list.{color} \n\nAdditionally, concerning Section 2.5 (Speaker Materials and Content), the policy states that markets using the MAPP Navigator system should upload approved speaker program content and its respective medical approval into the system. My question is *whether MAPP Navigator can serve as the sole archive for speaker program content, or if we need to maintain a separate local archive in a centralized local repository.:* {color:#70AD47}Agreed MN can be a central repository for external created content and its approval. However, for internal core deck, it still needs to go through GCMA and it is governed by medical team{color} \n\nBest, \n\nBarbara \n\n *{color:#0000C9} *Barbara Veberič, MPharm*{color}* \n\n{color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} \n\n{color:#0000C9}{color} \n\n *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} \n\n{color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} \n\n{color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} \n\n *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} \n\n *{color:#0000C7} {color}* \n\n *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ \n\n \n \n\n *From:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> \n *Sent:* Thursday, July 11, 2024 11:41 AM\n *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nDear Barbara \n\nI am very pleased to assist ! \n\nKindly note below my comments in Green & please don’t hesitate if you have further questions & clarifications. \n\nBest Regards \n| *{color:black} *Mariem Ben Abdallah*{color}*{color:black} \nEMEA Speaker Program & Policy operations Senior Manager{color} | \n| | \n| *{color:#0000C9} *Meetings, External Engagements & Travel (MEET)*{color}*{color:black} \nYour Journey, Our Priority!{color} | \n \n\n \n\n \n\n \n\n \n \n\n *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> \n *Sent:* Wednesday, July 10, 2024 9:20 AM\n *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nDear Mariem, \n\nThank you for your feedback. I still have a few remaining questions—apologies for the back-and-forth. \n* Is Version #3 of the GSPP, last updated in June 2024, the final version of the policy that will be effective from September 15, 2024?\n \n\n{color:#70AD47}YES effective date for all update will be SEP 15th{color} \n* Could we arrange local training sessions for relevant colleagues on the updated GSPP before its effective date, for example in early September?\n \n\n{color:#70AD47}MEET regional speaker program team will set up meetings to go through the policy updates and operational requirement with market stakeholders in next few weeks. In between Please feel free to contact myself and Eleni cc’d above if you have any other questions before the meeting. Also Marjeta in case there is any insight from local compliance perspective.{color} \n\nAdditionally, I have specific questions regarding the policy sections: \n* Section 2.3 Speakers – Approved List and Periodic Checks: Who can assist us in establishing and maintaining an approved list of speakers?\n \n\n{color:#70AD47}Medical Affairs colleague will be suuporting on that please refer to section 3 in Roles & Responsibilities as well in the policy as per the below screen shot{color} \n\n!image004.jpg|thumbnail! \n* {color:#70AD47} {color}{color:windowtext}Section 2.5 Speaker Materials and Content – Retention: Can we exclusively utilize the MAPP Navigator system for storing final versions of speaker program presentations and their corresponding medical approval emails? This would allow us to remove archives from our centralized local repositories as per the June 2024 version of the GSPP, which requires final, approved Speaker Program content to be uploaded into MAPP Navigator post-delivery, along with evidence of review and approval.{color} {color:#70AD47}This is the expectation from the policy : for Markets Using Mapp Navigator final, approved Speaker Program content must be uploaded in{color} \n \n\n \n\nThanks again! \n\nBest, \n\nBarbara \n\n *{color:#0000C9} *Barbara Veberič, MPharm*{color}* \n\n{color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} \n\n{color:#0000C9}{color} \n\n *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} \n\n{color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} \n\n{color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} \n\n *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} \n\n *{color:#0000C7} {color}* \n\n *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ \n\n \n \n\n *From:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> \n *Sent:* Tuesday, July 9, 2024 3:20 PM\n *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nDear Barbara. \n\nKindly see below my comments \n\nLet me know please if this clear for you. \n\nRegards \n\nMariem \n\n \n \n\n *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> \n *Sent:* Tuesday, July 9, 2024 8:17 AM\n *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nHi, \n\nThank you for clarifying. I also have a few additional questions: \n* Who can assist us with updating the local SOP if we choose to maintain it as a reference? {color:#2B9B62}as stated, the expectation is to remove the local SOP from Markets , All local requirements should have been transitioned to the Country Annex Portal ( CAP ) and any future updates should be made therein to respond to your question if you prefer keep it the Local CCL will be responsible for updating it in alignment with the global Policy : Local SOP should be as consistent as the CAP{color} \n* Will training on the updated version of the GSPP be available before its effective date? {color:#2B9B62}Global SPP Training including updates will be launched by the End of this year.{color} \n \n\nCan we obtain core slides for the updated GSPP to facilitate local-level training sessions? {color:#2B9B62}Please refer to the PPT attached in Global the communication - I am attaching also for your reference a different version of comparisons between the old policy language and the new language. It’s not every tiny change but includes the main updates that we made in the latest version. It’s not perfectly cleaned up, but hopefully will do the trick for now.{color} \n\n \n\nBest, \n\nBarbara \n\n *{color:#0000C9} *Barbara Veberič, MPharm*{color}* \n\n{color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} \n\n{color:#0000C9}{color} \n\n *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} \n\n{color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} \n\n{color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} \n\n *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} \n\n *{color:#0000C7} {color}* \n\n *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ \n\n \n \n\n *From:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]> \n *Sent:* Monday, July 8, 2024 11:56 AM\n *To:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>; Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* RE: Update to Global Speaker Program Policy \n\n \n\nDear Barbara. \n\nThank you for reaching out to us. \n\nIndeed, the update will be effective by September 15th, 2024, these updates should be reflected into the CAP of each Market ( Speaker Program Management section) ; if you may see in the update the expectation is to remove the local SOP & All local requirements should have been transitioned to the Country Annex Portal ( CAP ) and any future updates should be made therein unless the Market has a business reason to keep it as a reference hence in this case both local SOP & CAP must be accurate & consistent . \n\nWe as Speaker Program Team are the owner of this policy and certainly will liase with you & [@Bojic Kek, Marjeta|mailto:Marjeta.BojicKek@pfizer.com] to make this update consistent & clear across the Market . ( you can refer to the section Roles & Responsibilities in Page 15 -17 \n\nHope this clarifies your query , otherwise please let me know if you have further question happy to assist \n\n \n\nBest Regards \n| *{color:black} *Mariem Ben Abdallah*{color}*{color:black} \nEMEA Speaker Program & Policy operations Senior Manager{color} | \n| | \n| *{color:#0000C9} *Meetings, External Engagements & Travel (MEET)*{color}*{color:black} \nYour Journey, Our Priority!{color} | \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n\n *From:* Veberič, Barbara <[Barbara.Veberic@pfizer.com|mailto:Barbara.Veberic@pfizer.com]> \n *Sent:* Friday, July 5, 2024 11:46 AM\n *To:* Ben Abdallah, Mariem <[Mariem.BenAbdallah@pfizer.com|mailto:Mariem.BenAbdallah@pfizer.com]>; Kesisoglou, Eleni <[Eleni.Kesisoglou@pfizer.com|mailto:Eleni.Kesisoglou@pfizer.com]>\n *Cc:* Bojic Kek, Marjeta <[Marjeta.BojicKek@pfizer.com|mailto:Marjeta.BojicKek@pfizer.com]>\n *Subject:* FW: Update to Global Speaker Program Policy \n\n \n\nDear Colleagues, \n\nMy name is Barbara Veberič, and I have recently assumed the role of Medical Quality Governance Manager for the Adriatic region, succeeding Andrej Babnik, who will retire on July 15th. \n\nAndrej currently oversees the local Speaker Program Policy SOP for the Adriatic region and its MAPP Country Annexes for Slovenia, Croatia, Serbia, Bosnia, and CBC. As I take on this responsibility, I will serve as the interim owner until the updated version comes into effect. \n\nAccording to recent communications, there is a planned transfer of ownership for GSPP and an update to the policy, scheduled to take effect on September 15th, 2024. \n\n *I am seeking clarification on how these changes will impact our markets, particularly regarding the transfer of ownership, updates to local SOPs aligned with global policies, and revisions to local country annexes.* \n\nYour guidance during this transition period would be greatly appreciated, and I kindly request to be kept informed of any developments moving forward. \n\nThanks in advance! \n\nBest, \n\nBarbara \n\n *{color:#0000C9} *Barbara Veberič, MPharm*{color}* \n\n{color:#0000C9}Medical Quality Governance Manager (MQGM) Adriatic{color}{color:#0000C9}{color} \n\n{color:#0000C9}{color} \n\n *{color:#0000C7} *IDM Medical Affairs*{color}*{color:#0000C7}{color} \n\n{color:#0000C7}Pfizer Luxembourg SARL, Branch Office Ljubljana{color} \n\n{color:#0000C7}Letališka cesta 29a, 1000 Ljubljana, SIovenia{color} \n\n *{color:#0000C7} *M*{color}*{color:#0000C7}: +386 40 217 525{color} \n\n *{color:#0000C7} {color}* \n\n *{color:#A6A6A6} *CONFIDENTIALITY NOTICE*{color}*{color:#A6A6A6}: _This e-mail message from Pfizer Luxembourg SARL, Slovenia Branch Office (including all attachments) is for the sole use of the intended recipients and may contain confidential and privileged information. Any unauthorized review, use, disclosure, copying, or distribution is strictly prohibited. If you are not the intended recipient, please contact the sender by reply e-mail and destroy all copies. If you wish to report an adverse event, please contact_{color} [ _{color:#A6A6A6} _SVN.AEReporting@pfizer.com_{color}_|mailto:SVN.AEReporting@pfizer.com] _{color:#A6A6A6} _._{color}_ \n\n \n \n\n *From:* Global Speaker Program and Policy <[GlobalSpeakerProgramandPolicy@pfizer.com|mailto:GlobalSpeakerProgramandPolicy@pfizer.com]> \n *Sent:* Wednesday, 3 July 2024 21:14\n *To:* No reply <[noreply-OnMessage@pfizer.com|mailto:noreply-OnMessage@pfizer.com]>\n *Subject:* Update to Global Speaker Program Policy \n\n \n\nDear Colleagues. \n\n \n\nWe hope all is well at your end. As you may be aware, over the past few months, the Compliance team, and the Global Business Services - Meetings, External Engagements, and Travel - Process Integration & Controls team (GBS MEET PI&C) have been working to transition the ownership of the Global Speaker Program Policy, which was previously managed by the Global Risk Lead within Compliance. \n\nIn parallel, we received endorsement from the Commercial Quality & Risk Committee (CQRC) to build a team within the GBS MEET PI&C organization to support operationalization of speaker program requirements within markets across {color:black} the respective regions (EMEA, LATAM, and APAC).{color} The objective of this new team is to further streamline operations across markets while enhancing overall efficiency and strengthening risk management. We look forward to receiving your continued attention and support during this crucial period of transition as the team assumes their new responsibilities. Below is an overview of the GBS MEET PI&C team that will support the Global Policy and Process Oversight:{color:black}{color} \n| *Global Contacts* | ? Brian Badal (Sr. Director, Global MEET PI&C Lead) ? Huzefa Rangwala (Director, MEET PI&C and Global Speaker Program Policy Owner) | \n| *Regional Contacts* | *APAC :* * Katherine Yang (Sr. Manager - MEET PI&C APAC Regional Lead)* Li Peng Lin (Sr. Analyst - MEET PI&C APAC Region) | \n| *EMEA (excluding Spain and Portugal):* * Mariem Ben Abdallah (Sr. Manager - MEET PI&C EMEA Regional Lead)\n* Eleni Kesisoglou (Sr. Analyst - EMEA Region) | \n| *LATAM : and including Canada, Spain, and Portugal* * Andrea Villalobos (Director - MEET PI&C)\n* Norman Leandro (Sr. Manager - MEET LATAM Regional Lead)\n* Alejandra Perez (Sr. Analyst - LATAM Region) | \n \n\n \n\nAlison Page (Global Risk Lead for HCP Consultancies) will continue to partner and support the GBS MEET PI&C team in managing the Global Speaker Program Policy going forward. \n\n \n\nWe have made updates to the Global Speaker Program Policy to incorporate updated roles & responsibilities of various functions associated with speaker programs, including the new responsibilities for GBS MEET PI&C, as well as to clarify certain policy requirements. The updated policy will become effective on *{color:#00B050} *September 15, 2024.*{color}* \n\n \n\nThe updated policy and a summary of the recent updates are attached to this email. Please cascade this message to your team and other relevant colleagues, as needed. In the coming days, GBS MEET PI&C Regional teams will be reaching out to colleagues in the Markets to support the transition and will partner with market Compliance to update the requirements within the local Country Annex, as needed. \n\n \n\nYour cooperation and commitment to adapting these changes are greatly appreciated. \n\n \n\nIf you have any questions or concerns, please feel free to reach out to the above mentioned contact points or to our mailbox at [GlobalSpeakerProgramandPolicy@pfizer.com|mailto:GlobalSpeakerProgramandPolicy@pfizer.com]. \n\n \n\n \n\n *Best regards,* \n\n *Brian Badal, GBS MEET PI&C Lead & Alison Page, Global Risk Lead*", - "customfield_11100": null, - "customfield_12551": null, - "customfield_11101": [], - "customfield_11102": { - "_links": { - "jiraRest": "https://jira.pfizer.com/rest/api/2/issue/2359796", - "web": "https://jira.pfizer.com/servicedesk/customer/portal/241/HSM-1235", - "self": "https://jira.pfizer.com/rest/servicedeskapi/request/2359796" - }, - "requestType": { - "id": "4434", - "_links": { - "self": "https://jira.pfizer.com/rest/servicedeskapi/servicedesk/241/requesttype/4434" - }, - "name": "Service Request", - "description": "General ticket to support our Clients", - "helpText": "", - "serviceDeskId": "241", - "groupIds": [ - "832" - ], - "icon": { - "id": "12235", - "_links": { - "iconUrls": { - "48x48": "https://jira.pfizer.com/secure/viewavatar?avatarType=SD_REQTYPE&size=large&avatarId=12235", - "24x24": "https://jira.pfizer.com/secure/viewavatar?avatarType=SD_REQTYPE&size=small&avatarId=12235", - "16x16": "https://jira.pfizer.com/secure/viewavatar?avatarType=SD_REQTYPE&size=xsmall&avatarId=12235", - "32x32": "https://jira.pfizer.com/secure/viewavatar?avatarType=SD_REQTYPE&size=medium&avatarId=12235" - } - } - } - }, - "currentStatus": { - "status": "Canceled", - "statusDate": { - "iso8601": "2024-07-26T14:13:18+0200", - "jira": "2024-07-26T08:13:18.423-0400", - "friendly": "26/Jul/24 2:13 PM", - "epochMillis": 1721995998423 - } - } - }, - "customfield_11103": [], - "customfield_11104": null, - "customfield_12546": null, - "customfield_12428": null, - "customfield_28542": null, - "customfield_29756": null, - "customfield_29759": null, - "customfield_29757": null, - "customfield_28669": null, - "customfield_29758": null, - "summary": "RE: MDM Database Export", - "customfield_30502": null, - "customfield_11331": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/11919", - "value": "N/A", - "id": "11919", - "disabled": false - }, - "customfield_10001": null, - "customfield_11333": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/11922", - "value": "N/A", - "id": "11922", - "disabled": false - }, - "customfield_12421": null, - "customfield_12303": null, - "customfield_30501": null, - "customfield_11334": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/11924", - "value": "No", - "id": "11924", - "disabled": false - }, - "customfield_11204": null, - "customfield_11326": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/11910", - "value": "Open Source", - "id": "11910", - "disabled": false - }, - "customfield_11205": null, - "customfield_12414": null, - "customfield_14717": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/17739", - "value": "Raw", - "id": "17739", - "disabled": false - }, - "customfield_29760": null, - "customfield_27101": null, - "customfield_27100": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/72204", - "value": "Unknown", - "id": "72204", - "disabled": false - }, - "customfield_27102": null, - "customfield_29769": null, - "customfield_15001": "None", - "customfield_18515": "To be provided over mail", - "customfield_18516": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27211", - "value": "Daily", - "id": "27211", - "disabled": false - }, - "customfield_15005": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/18005", - "value": "No", - "id": "18005", - "disabled": false - }, - "customfield_15002": null, - "fixVersions": [ - { - "self": "https://jira.pfizer.com/rest/api/2/version/42016", - "id": 42016, - "name": "BAU", - "description": "", - "archived": false, - "startDate": null, - "released": false, - "releaseDate": null, - "projectId": null, - "namedValue": "BAU" - } - ], - "customfield_15008": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/18009", - "value": "Pfizer", - "id": "18009", - "disabled": false - }, - "customfield_18512": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27208", - "value": "UTC", - "id": "27208", - "disabled": true - }, - "customfield_19724": null, - "customfield_18514": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27229", - "value": "Daily", - "id": "27229", - "disabled": false - }, - "customfield_18508": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/27202", - "value": "Brazil", - "id": "27202", - "disabled": false - }, - "customfield_26706": null, - "customfield_26705": null, - "customfield_26708": null, - "customfield_16320": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/20779", - "value": "Unknown", - "id": "20779", - "disabled": false - }, - "customfield_27917": null, - "customfield_26707": null, - "customfield_15350": null, - "customfield_16204": null, - "customfield_18626": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/25129", - "value": "No", - "id": "25129", - "disabled": false - }, - "customfield_19716": null, - "priority": { - "self": "https://jira.pfizer.com/rest/api/2/priority/3", - "id": 3, - "name": "Medium", - "iconUrl": "https://jira.pfizer.com/images/icons/priorities/medium.svg", - "namedValue": "Medium" - }, - "customfield_19711": null, - "customfield_16205": null, - "versions": [], - "customfield_19709": null, - "customfield_15340": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/18648", - "value": "N/A", - "id": "18648", - "disabled": false - }, - "customfield_16556": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/22898", - "value": "--", - "id": "22898", - "disabled": false - }, - "customfield_16555": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/22897", - "value": "--", - "id": "22897", - "disabled": false - }, - "customfield_17402": null, - "customfield_16554": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/22896", - "value": "--", - "id": "22896", - "disabled": false - }, - "customfield_16433": null, - "customfield_16558": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/23000", - "value": "--", - "id": "23000", - "disabled": false - }, - "customfield_16557": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/22899", - "value": "--", - "id": "22899", - "disabled": false - }, - "customfield_26601": null, - "customfield_26600": null, - "customfield_16420": "10904_*:*_1_*:*_0_*|*_11100_*:*_1_*:*_11024926", - "customfield_19813": null, - "customfield_19814": null, - "customfield_16422": null, - "customfield_16301": "None", - "aggregateprogress": { - "progress": 0, - "total": 0 - }, - "customfield_14128": null, - "customfield_16427": "None", - "customfield_19811": null, - "customfield_19812": null, - "customfield_12293": null, - "customfield_15202": null, - "customfield_16407": null, - "customfield_27715": null, - "created": 1721984973497, - "customfield_12287": [ - { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/12718", - "value": "Not Met", - "id": "12718", - "disabled": false - } - ], - "customfield_18816": null, - "customfield_18817": null, - "customfield_28819": null, - "customfield_15300": null, - "customfield_12399": { - "self": "https://jira.pfizer.com/rest/api/2/customFieldOption/13599", - "value": "Test", - "id": "13599", - "disabled": false - }, - "customfield_15424": null, - "customfield_14208": null, - "customfield_27857": null, - "customfield_27856": null, - "customfield_18801": null, - "customfield_15411": null, - "customfield_12385": null, - "customfield_18802": null, - "customfield_18803": null, - "customfield_15410": null, - "customfield_12386": null, - "customfield_15413": null, - "customfield_18800": null, - "customfield_15414": null, - "customfield_14204": null, - "customfield_24112": null - }, - "renderedFields": {} -} \ No newline at end of file diff --git a/jira-webhook-llm.py b/jira-webhook-llm.py index b45f7f4..cce29af 100644 --- a/jira-webhook-llm.py +++ b/jira-webhook-llm.py @@ -1,279 +1,102 @@ -import os -import json -from typing import Optional, List, Union - -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel, Field, ConfigDict, validator +from fastapi import FastAPI, Request, HTTPException +from pydantic import BaseModel +from fastapi.responses import JSONResponse from loguru import logger +import uuid +import sys +from typing import Optional +from datetime import datetime +import asyncio +from functools import wraps -# Import your new settings object from config import settings +from webhooks.handlers import JiraWebhookHandler +from llm.models import JiraWebhookPayload +from logging_config import configure_logging - -# LangChain imports -from langchain_ollama import OllamaLLM -from langchain_openai import ChatOpenAI -from langchain_core.prompts import PromptTemplate -from langchain_core.output_parsers import JsonOutputParser -from pydantic import BaseModel as LCBaseModel -from pydantic import field_validator - -# Langfuse imports -from langfuse import Langfuse, get_client -from langfuse.langchain import CallbackHandler - -# LANGFUSE_PUBLIC_KEY="pk_lf_..." -# LANGFUSE_SECRET_KEY="sk_lf_..." -# LANGFUSE_HOST="https://cloud.langfuse.com" # Or "https://us.cloud.langfuse.com" for US region, or your self-hosted instance - -langfuse = Langfuse( - secret_key="sk-lf-55d5fa70-e2d3-44d0-ae76-48181126d7ed", - public_key="pk-lf-0f6178ee-e6aa-4cb7-a433-6c00c6512874", - host="https://cloud.langfuse.com" -) - -# Initialize Langfuse client (optional, get_client() uses environment variables by default) -# It's good practice to initialize it early to ensure connection. try: - langfuse_client = get_client() - if langfuse_client.auth_check(): - logger.info("Langfuse client authenticated successfully.") - else: - logger.warning("Langfuse authentication failed. Check your API keys and host.") + app = FastAPI() + logger.info("FastAPI application initialized") except Exception as e: - logger.error(f"Failed to initialize Langfuse client: {e}") - # Depending on your tolerance, you might want to exit or continue without tracing - # For now, we'll just log and continue, but traces won't be sent. + logger.error(f"Error initializing FastAPI: {str(e)}") + raise +# Initialize logging +configure_logging(log_level=settings.log.level) -# --- Pydantic Models for Jira Payload and LLM Output --- -# Configuration for Pydantic to handle camelCase to snake_case conversion -class JiraWebhookPayload(BaseModel): - model_config = ConfigDict(alias_generator=lambda x: ''.join(word.capitalize() if i > 0 else word for i, word in enumerate(x.split('_'))), populate_by_name=True) +def retry(max_retries: int = 3, delay: float = 1.0): + """Decorator for retrying failed operations""" + def decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + last_error = None + for attempt in range(max_retries): + try: + return await func(*args, **kwargs) + except Exception as e: + last_error = e + logger.warning(f"Attempt {attempt + 1} failed: {str(e)}") + if attempt < max_retries - 1: + await asyncio.sleep(delay * (attempt + 1)) + raise last_error + return wrapper + return decorator - issueKey: str - summary: str - description: Optional[str] = None - comment: Optional[str] = None # Assuming this is the *new* comment that triggered the webhook - labels: Optional[Union[List[str], str]] = [] +class ErrorResponse(BaseModel): + error_id: str + timestamp: str + status_code: int + message: str + details: Optional[str] = None + +@app.middleware("http") +async def error_handling_middleware(request: Request, call_next): + request_id = str(uuid.uuid4()) + logger.bind(request_id=request_id).info(f"Request started: {request.method} {request.url}") - @field_validator('labels', mode='before') # `pre=True` becomes `mode='before'` - @classmethod # V2 validators must be classmethods - def convert_labels_to_list(cls, v): - if isinstance(v, str): - return [v] - return v or [] # Return an empty list if v is None/empty - - status: Optional[str] = None - assignee: Optional[str] = None - updated: Optional[str] = None # Timestamp string + try: + response = await call_next(request) + return response + except HTTPException as e: + logger.error(f"HTTP Error: {e.status_code} - {e.detail}") + error_response = ErrorResponse( + error_id=request_id, + timestamp=datetime.utcnow().isoformat(), + status_code=e.status_code, + message=e.detail, + details=str(e) + ) + return JSONResponse(status_code=e.status_code, content=error_response.model_dump()) + except Exception as e: + logger.error(f"Unexpected error: {str(e)}") + error_response = ErrorResponse( + error_id=request_id, + timestamp=datetime.utcnow().isoformat(), + status_code=500, + message="Internal Server Error", + details=str(e) + ) + return JSONResponse(status_code=500, content=error_response.model_dump()) +webhook_handler = JiraWebhookHandler() -# Define the structure of the LLM's expected JSON output -class AnalysisFlags(LCBaseModel): - hasMultipleEscalations: bool = Field(description="Is there evidence of multiple escalation attempts or channels?") - requiresUrgentAttention: bool = Field(description="Does the issue convey a sense of urgency beyond standard priority?") - customerSentiment: Optional[str] = Field(description="Overall customer sentiment (e.g., 'neutral', 'frustrated', 'calm').") - suggestedLabels: List[str] = Field(description="List of suggested Jira labels, e.g., ['escalated', 'high-customer-impact'].") - summaryOfConcerns: Optional[str] = Field(description="A concise summary of the key concerns or problems in the ticket.") - - -# --- LLM Setup (Now dynamic based on config) --- -llm = None -if settings.llm_mode == 'openai': - logger.info(f"Initializing ChatOpenAI with model: {settings.openai_model}") - llm = ChatOpenAI( - model=settings.openai_model, - temperature=0.7, - max_tokens=2000, - api_key=settings.openai_api_key, - base_url=settings.openai_api_base_url - ) -elif settings.llm_mode == 'ollama': - logger.info(f"Initializing OllamaLLM with model: {settings.ollama_model} at {settings.ollama_base_url}") - llm = OllamaLLM( - model=settings.ollama_model, - base_url=settings.ollama_base_url, - streaming=False - ) - -# This check is now redundant because config.py would have exited, but it's good for clarity. -if llm is None: - logger.error("LLM could not be initialized. Exiting.") - sys.exit(1) - - -app = FastAPI() - - -# Set up Output Parser for structured JSON -parser = JsonOutputParser(pydantic_object=AnalysisFlags) - -# Prompt Template for LLM -prompt_template = PromptTemplate( - template=""" -You are an AI assistant designed to analyze Jira ticket details and extract key flags and sentiment. -Analyze the following Jira ticket information and provide your analysis in a JSON format. -Ensure the JSON strictly adheres to the specified schema. - -Consider the overall context of the ticket and specifically the latest comment if provided. - -Issue Key: {issueKey} -Summary: {summary} -Description: {description} -Status: {status} -Existing Labels: {labels} -Assignee: {assignee} -Last Updated: {updated} -Latest Comment (if applicable): {comment} - -**Analysis Request:** -- Determine if there are signs of multiple escalation attempts in the descriptions or comments. -- Assess if the issue requires urgent attention based on language or context from the summary, description, or latest comment. -- Summarize the overall customer sentiment evident in the issue. -- Suggest relevant Jira labels that should be applied to this issue. -- Provide a concise summary of the key concerns or problems described in the ticket. -- Generate a concise, objective comment (max 2-3 sentences) suitable for directly adding to the Jira ticket, summarizing the AI's findings. - -{format_instructions} -""", - input_variables=[ - "issueKey", "summary", "description", "status", "labels", - "assignee", "updated", "comment" - ], - partial_variables={"format_instructions": parser.get_format_instructions()}, -) - -# Chain for LLM invocation -analysis_chain = prompt_template | llm | parser - -# --- Webhook Endpoint --- @app.post("/jira-webhook") async def jira_webhook_handler(payload: JiraWebhookPayload): - # Initialize Langfuse CallbackHandler for this request - # This ensures each webhook invocation gets its own trace in Langfuse - langfuse_handler = CallbackHandler() + return await webhook_handler.handle_webhook(payload) - try: - logger.info(f"Received webhook for Jira issue: {payload.issueKey}") - - # Prepare payload for LangChain: - # 1. Use the 'comment' field directly if it exists, as it's typically the trigger. - # 2. Convert Optional fields to usable strings for the prompt. - - # This mapping handles potential None values in the payload - llm_input = { - "issueKey": payload.issueKey, - "summary": payload.summary, - "description": payload.description if payload.description else "No description provided.", - "status": payload.status if payload.status else "Unknown", - "labels": ", ".join(payload.labels) if payload.labels else "None", - "assignee": payload.assignee if payload.assignee else "Unassigned", - "updated": payload.updated if payload.updated else "Unknown", - "comment": payload.comment if payload.comment else "No new comment provided." - } - - # Pass data to LangChain for analysis - # Using ainvoke for async execution - # Add the Langfuse handler to the config of the ainvoke call - analysis_result = await analysis_chain.ainvoke( - llm_input, - config={ - "callbacks": [langfuse_handler], - "metadata": { - "trace_name": f"JiraWebhook-{payload.issueKey}" - } - } - ) - - logger.debug(f"LLM Analysis Result for {payload.issueKey}: {json.dumps(analysis_result, indent=2)}") - - return {"status": "success", "analysis_flags": analysis_result} - - except Exception as e: - logger.error(f"Error processing webhook: {e}") - import traceback - traceback.print_exc() # Print full traceback for debugging - - # In case of an error, you might want to log it to Langfuse as well - # You can update the trace with an error - if langfuse_handler.trace: # Check if the trace was started - langfuse_handler.trace.update( - status_message=f"Error: {str(e)}", - level="ERROR" - ) - - raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}") - finally: - # It's good practice to flush the Langfuse client to ensure all events are sent - # This is especially important in short-lived processes or serverless functions - # For a long-running FastAPI app, the client's internal queue usually handles this - # but explicit flush can be useful for immediate visibility or during testing. - if langfuse_client: - langfuse_client.flush() - - -# To run this: -# 1. Set OPENAI_API_KEY, LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_HOST environment variables -# 2. Start FastAPI: uvicorn main:app --host 0.0.0.0 --port 8000 --reload @app.post("/test-llm") async def test_llm(): """Test endpoint for LLM integration""" - # Correctly initialize the Langfuse CallbackHandler. - # It inherits the client configuration from the global 'langfuse' instance. - # If you need to name the trace, you do so in the 'ainvoke' call's metadata. - langfuse_handler = CallbackHandler( - # The constructor does not take 'trace_name'. - # Remove it from here. + test_payload = JiraWebhookPayload( + issueKey="TEST-123", + summary="Test issue", + description="This is a test issue for LLM integration", + comment="Testing OpenAI integration with Langfuse", + labels=["test"], + status="Open", + assignee="Tester", + updated="2025-07-04T21:40:00Z" ) + return await webhook_handler.handle_webhook(test_payload) - test_payload = { - "issueKey": "TEST-123", - "summary": "Test issue", - "description": "This is a test issue for LLM integration", - "comment": "Testing OpenAI integration with Langfuse", - "labels": ["test"], - "status": "Open", - "assignee": "Tester", - "updated": "2025-07-04T21:40:00Z" - } - - try: - llm_input = { - "issueKey": test_payload["issueKey"], - "summary": test_payload["summary"], - "description": test_payload["description"], - "status": test_payload["status"], - "labels": ", ".join(test_payload["labels"]), - "assignee": test_payload["assignee"], - "updated": test_payload["updated"], - "comment": test_payload["comment"] - } - - # To name the trace, you pass it in the config's metadata - result = await analysis_chain.ainvoke( - llm_input, - config={ - "callbacks": [langfuse_handler], - "metadata": { - "trace_name": "TestLLM" # Correct way to name the trace - } - } - ) - return { - "status": "success", - "result": result - } - except Exception as e: - if langfuse_handler.trace: - langfuse_handler.trace.update( - status_message=f"Error in test-llm: {str(e)}", - level="ERROR" - ) - logger.error(f"Error in /test-llm: {e}") - return { - "status": "error", - "message": str(e) - } - finally: - if langfuse_client: - langfuse_client.flush() \ No newline at end of file +# To run this: +# 1. Start FastAPI: uvicorn main:app --host 0.0.0.0 --port 8000 --reload \ No newline at end of file diff --git a/llm/chains.py b/llm/chains.py new file mode 100644 index 0000000..34313ae --- /dev/null +++ b/llm/chains.py @@ -0,0 +1,74 @@ +from langchain_ollama import OllamaLLM +from langchain_openai import ChatOpenAI +from langchain_core.prompts import PromptTemplate +from langchain_core.output_parsers import JsonOutputParser +from loguru import logger + +from config import settings +from .models import AnalysisFlags + +# Initialize LLM +llm = None +if settings.llm.mode == 'openai': + logger.info(f"Initializing ChatOpenAI with model: {settings.openai_model}") + llm = ChatOpenAI( + model=settings.openai_model, + temperature=0.7, + max_tokens=2000, + api_key=settings.openai_api_key, + base_url=settings.openai_api_base_url + ) +elif settings.llm.mode == 'ollama': + logger.info(f"Initializing OllamaLLM with model: {settings.llm.ollama_model} at {settings.llm.ollama_base_url}") + llm = OllamaLLM( + model=settings.llm.ollama_model, + base_url=settings.llm.ollama_base_url, + streaming=False + ) + +if llm is None: + logger.error("LLM could not be initialized. Exiting.") + sys.exit(1) + +# Set up Output Parser for structured JSON +parser = JsonOutputParser(pydantic_object=AnalysisFlags) + +# Load prompt template from file +def load_prompt_template(version="v1.0.0"): + try: + with open(f"llm/prompts/jira_analysis_{version}.txt", "r") as f: + template = f.read() + return PromptTemplate( + template=template, + input_variables=[ + "issueKey", "summary", "description", "status", "labels", + "assignee", "updated", "comment" + ], + partial_variables={"format_instructions": parser.get_format_instructions()}, + ) + except Exception as e: + logger.error(f"Failed to load prompt template: {str(e)}") + raise + +# Fallback prompt template +FALLBACK_PROMPT = PromptTemplate( + template="Please analyze this Jira ticket and provide a basic summary.", + input_variables=["issueKey", "summary"] +) + +# Create chain with fallback mechanism +def create_analysis_chain(): + try: + prompt_template = load_prompt_template() + return prompt_template | llm | parser + except Exception as e: + logger.warning(f"Using fallback prompt due to error: {str(e)}") + return FALLBACK_PROMPT | llm | parser + +# Initialize analysis chain +analysis_chain = create_analysis_chain() + +# Response validation function +def validate_response(response: dict) -> bool: + required_fields = ["hasMultipleEscalations", "customerSentiment"] + return all(field in response for field in required_fields) \ No newline at end of file diff --git a/llm/models.py b/llm/models.py new file mode 100644 index 0000000..ae57c4a --- /dev/null +++ b/llm/models.py @@ -0,0 +1,26 @@ +from typing import Optional, List, Union +from pydantic import BaseModel, ConfigDict, field_validator, Field + +class JiraWebhookPayload(BaseModel): + model_config = ConfigDict(alias_generator=lambda x: ''.join(word.capitalize() if i > 0 else word for i, word in enumerate(x.split('_'))), populate_by_name=True) + + issueKey: str + summary: str + description: Optional[str] = None + comment: Optional[str] = None + labels: Optional[Union[List[str], str]] = [] + + @field_validator('labels', mode='before') + @classmethod + def convert_labels_to_list(cls, v): + if isinstance(v, str): + return [v] + return v or [] + + status: Optional[str] = None + assignee: Optional[str] = None + updated: Optional[str] = None + +class AnalysisFlags(BaseModel): + hasMultipleEscalations: bool = Field(description="Is there evidence of multiple escalation attempts?") + customerSentiment: Optional[str] = Field(description="Overall customer sentiment (e.g., 'neutral', 'frustrated', 'calm').") \ No newline at end of file diff --git a/llm/prompt_tests.py b/llm/prompt_tests.py new file mode 100644 index 0000000..159cbae --- /dev/null +++ b/llm/prompt_tests.py @@ -0,0 +1,29 @@ +import unittest +from llm.chains import load_prompt_template, validate_response +from llm.models import AnalysisFlags + +class PromptTests(unittest.TestCase): + def test_prompt_loading(self): + """Test that prompt template loads correctly""" + try: + template = load_prompt_template() + self.assertIsNotNone(template) + self.assertIn("issueKey", template.input_variables) + except Exception as e: + self.fail(f"Prompt loading failed: {str(e)}") + + def test_response_validation(self): + """Test response validation logic""" + valid_response = { + "hasMultipleEscalations": False, + "customerSentiment": "neutral" + } + invalid_response = { + "customerSentiment": "neutral" + } + + self.assertTrue(validate_response(valid_response)) + self.assertFalse(validate_response(invalid_response)) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/llm/prompts/jira_analysis_v1.0.0.txt b/llm/prompts/jira_analysis_v1.0.0.txt new file mode 100644 index 0000000..fae631a --- /dev/null +++ b/llm/prompts/jira_analysis_v1.0.0.txt @@ -0,0 +1,23 @@ +You are an AI assistant designed to analyze Jira ticket details containe email correspondence and extract key flags and sentiment. +Analyze the following Jira ticket information and provide your analysis in a JSON format. +Ensure the JSON strictly adheres to the specified schema. + +Consider the overall context of the ticket and specifically the latest comment if provided. + +Issue Key: {issueKey} +Summary: {summary} +Description: {description} +Status: {status} +Existing Labels: {labels} +Assignee: {assignee} +Last Updated: {updated} +Latest Comment (if applicable): {comment} + +**Analysis Request:** +- Determine if there are signs of multiple escalation attempts in the descriptions or comments with regards to HUB team. Escalation to other teams are not considered. +-- Usually multiple requests one after another are being called by the same user in span of hours or days asking for immediate help of HUB team. Normall discussion, responses back and forth, are not considered as a escalation. +- Assess if the issue requires urgent attention based on language or context from the summary, description, or latest comment. +-- Usually means that Customer is asking for help due to upcoming deadlines, other high priority issues which are blocked due to our stall. +- Summarize the overall customer sentiment evident in the issue. Analyse tone of responses, happiness, gratefullnes, iritation, etc. + +{format_instructions} \ No newline at end of file diff --git a/logging_config.py b/logging_config.py new file mode 100644 index 0000000..2085822 --- /dev/null +++ b/logging_config.py @@ -0,0 +1,46 @@ +import sys +from loguru import logger + +logger.configure( + extra={"request_id": "N/A"} +) +import os +from pathlib import Path +from datetime import datetime +from typing import Optional + +def configure_logging(log_level: str = "INFO", log_dir: Optional[str] = None): + """Configure structured logging for the application""" + + # Default log directory + if not log_dir: + log_dir = os.getenv("LOG_DIR", "logs") + + # Create log directory if it doesn't exist + Path(log_dir).mkdir(parents=True, exist_ok=True) + + # Log file path with timestamp + log_file = Path(log_dir) / f"jira-webhook-llm_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" + + # Remove default logger + logger.remove() + + # Add console logger + logger.add( + sys.stdout, + level=log_level, + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {extra[request_id]} | {message}", + colorize=True + ) + + # Add file logger + logger.add( + str(log_file), + level=log_level, + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {extra[request_id]} | {message}", + rotation="100 MB", + retention="30 days", + compression="zip" + ) + + logger.info("Logging configured successfully") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ed1e95e..3658522 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,18 @@ fastapi==0.111.0 pydantic==2.9.0 # Changed from 2.7.4 to meet ollama's requirement pydantic-settings==2.0.0 +langchain==0.3.26 langchain-ollama==0.3.3 langchain-openai==0.3.27 langchain-core==0.3.68 +langfuse==3.1.3 uvicorn==0.30.1 python-multipart==0.0.9 # Good to include for FastAPI forms loguru==0.7.3 -langfuse==3.1.3 \ No newline at end of file +# Testing dependencies +unittest2>=1.1.0 +# Testing dependencies +pytest==8.2.0 +pytest-asyncio==0.23.5 +pytest-cov==4.1.0 +httpx==0.27.0 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..270cf5b --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Initialize tests package \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..86f535e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,20 @@ +import pytest +from fastapi.testclient import TestClient +from jira_webhook_llm import app + +@pytest.fixture +def test_client(): + return TestClient(app) + +@pytest.fixture +def mock_jira_payload(): + return { + "issueKey": "TEST-123", + "summary": "Test Issue", + "description": "Test Description", + "comment": "Test Comment", + "labels": ["test"], + "status": "Open", + "assignee": "Tester", + "updated": "2025-07-13T12:00:00Z" + } \ No newline at end of file diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..643c851 --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,38 @@ +import pytest +from fastapi import HTTPException +from jira_webhook_llm import app +from llm.models import JiraWebhookPayload + +def test_error_handling_middleware(test_client, mock_jira_payload): + # Test 404 error handling + response = test_client.post("/nonexistent-endpoint", json={}) + assert response.status_code == 404 + assert "error_id" in response.json() + + # Test validation error handling + invalid_payload = mock_jira_payload.copy() + invalid_payload.pop("issueKey") + response = test_client.post("/jira-webhook", json=invalid_payload) + assert response.status_code == 422 + assert "details" in response.json() + +def test_webhook_handler(test_client, mock_jira_payload): + # Test successful webhook handling + response = test_client.post("/jira-webhook", json=mock_jira_payload) + assert response.status_code == 200 + assert "response" in response.json() + +def test_llm_test_endpoint(test_client): + # Test LLM test endpoint + response = test_client.post("/test-llm") + assert response.status_code == 200 + assert "response" in response.json() + +def test_retry_decorator(): + # Test retry decorator functionality + @app.retry(max_retries=3) + async def failing_function(): + raise Exception("Test error") + + with pytest.raises(Exception): + failing_function() \ No newline at end of file diff --git a/tests/test_llm_validation.py b/tests/test_llm_validation.py new file mode 100644 index 0000000..30cdd91 --- /dev/null +++ b/tests/test_llm_validation.py @@ -0,0 +1,34 @@ +import pytest +from fastapi.testclient import TestClient +from jira_webhook_llm import app +from llm.models import JiraWebhookPayload + +def test_llm_response_format(test_client, mock_jira_payload): + response = test_client.post("/jira-webhook", json=mock_jira_payload) + assert response.status_code == 200 + response_data = response.json() + + # Validate response structure + assert "response" in response_data + assert "analysis" in response_data["response"] + assert "recommendations" in response_data["response"] + assert "status" in response_data["response"] + +def test_llm_response_content_validation(test_client, mock_jira_payload): + response = test_client.post("/jira-webhook", json=mock_jira_payload) + response_data = response.json() + + # Validate content types + assert isinstance(response_data["response"]["analysis"], str) + assert isinstance(response_data["response"]["recommendations"], list) + assert isinstance(response_data["response"]["status"], str) + +def test_llm_error_handling(test_client): + # Test with invalid payload + invalid_payload = {"invalid": "data"} + response = test_client.post("/jira-webhook", json=invalid_payload) + assert response.status_code == 422 + + # Test with empty payload + response = test_client.post("/jira-webhook", json={}) + assert response.status_code == 422 \ No newline at end of file diff --git a/webhooks/handlers.py b/webhooks/handlers.py new file mode 100644 index 0000000..7b576ab --- /dev/null +++ b/webhooks/handlers.py @@ -0,0 +1,81 @@ +from fastapi import HTTPException +from loguru import logger +import json +from typing import Optional, List, Union +from pydantic import BaseModel, ConfigDict, field_validator +from datetime import datetime + +from config import settings +from llm.models import JiraWebhookPayload, AnalysisFlags +from llm.chains import analysis_chain + +class BadRequestError(HTTPException): + def __init__(self, detail: str): + super().__init__(status_code=400, detail=detail) + +class RateLimitError(HTTPException): + def __init__(self, detail: str): + super().__init__(status_code=429, detail=detail) + +class ValidationError(HTTPException): + def __init__(self, detail: str): + super().__init__(status_code=422, detail=detail) + +class JiraWebhookHandler: + def __init__(self): + self.analysis_chain = analysis_chain + + async def handle_webhook(self, payload: JiraWebhookPayload): + try: + if not payload.issueKey: + raise BadRequestError("Missing required field: issueKey") + + if not payload.summary: + raise BadRequestError("Missing required field: summary") + + logger.bind( + issue_key=payload.issueKey, + timestamp=datetime.utcnow().isoformat() + ).info("Received webhook") + + llm_input = { + "issueKey": payload.issueKey, + "summary": payload.summary, + "description": payload.description if payload.description else "No description provided.", + "status": payload.status if payload.status else "Unknown", + "labels": ", ".join(payload.labels) if payload.labels else "None", + "assignee": payload.assignee if payload.assignee else "Unassigned", + "updated": payload.updated if payload.updated else "Unknown", + "comment": payload.comment if payload.comment else "No new comment provided." + } + + try: + analysis_result = await self.analysis_chain.ainvoke(llm_input) + + # Validate LLM response + if not validate_response(analysis_result): + logger.warning(f"Invalid LLM response format for {payload.issueKey}") + analysis_result = { + "hasMultipleEscalations": False, + "customerSentiment": "neutral" + } + + logger.debug(f"LLM Analysis Result for {payload.issueKey}: {json.dumps(analysis_result, indent=2)}") + return {"status": "success", "analysis_flags": analysis_result} + + except Exception as e: + logger.error(f"LLM processing failed for {payload.issueKey}: {str(e)}") + return { + "status": "error", + "analysis_flags": { + "hasMultipleEscalations": False, + "customerSentiment": "neutral" + }, + "error": str(e) + } + + except Exception as e: + logger.error(f"Error processing webhook: {str(e)}") + import traceback + logger.error(f"Stack trace: {traceback.format_exc()}") + raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}") \ No newline at end of file