From b924a623931cc392b4c06e0f4a0d7d53e749caa7 Mon Sep 17 00:00:00 2001 From: sandeshit Date: Mon, 3 Aug 2026 15:00:19 +0545 Subject: [PATCH 1/5] feat(summary): add summarization fields for imminent type. --- dref/summary.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/dref/summary.py b/dref/summary.py index 4976ddece..73c18133d 100644 --- a/dref/summary.py +++ b/dref/summary.py @@ -18,10 +18,8 @@ ENCODING_NAME = "cl100k_base" -MAX_OUTPUT_CHARS_PER_FIELD = 1500 MAX_INPUT_TOKENS = 10000 - # The models a DrefSummary can be generated from. DrefSummarySource = Union[Dref, DrefOperationalUpdate, DrefFinalReport] @@ -44,7 +42,8 @@ "You are an IFRC expert analyst specializing in DREF (Disaster Response Emergency Fund) " "operations. Analyze the provided DREF data and produce clear, professional humanitarian " "summaries suitable for IFRC staff and National Society personnel. Use only the information " - "provided in the data; do not invent facts, figures, or details." + "provided in the data; do not invent facts, figures, or details. Where supporting information " + "for a section is absent, return an empty string for that section rather than speculating." ) # Section prompt builders @@ -90,7 +89,7 @@ def _build_lessons_learned_prompt(**kwargs) -> str: } GLOBAL_PROMPT = ( - "The DREF data above is organised by summary section. Using ONLY that data, write five concise " + "The DREF data above is organised by summary section. Using ONLY that data, write five " "summary sections. Return a single JSON object (and nothing else) with exactly these keys, each " "summarising the block of the same name:\n" "\n" @@ -109,8 +108,15 @@ def _build_lessons_learned_prompt(**kwargs) -> str: "- Summarise only what each section's data actually contains; do not add topics or details it " "does not mention.\n" "- Preserve every specific figure, location and timeframe from the source; never fabricate them.\n" - "- Each value must be plain text (no markdown, no bullet lists, no nested JSON): one " - "well-structured paragraph in professional humanitarian language.\n" + "- If a section's data block is empty or holds no usable content, set that key to an empty " + "string. Never write a sentence stating that data is missing, not provided or not recorded.\n" + "- Scale each section's length to how much its source data actually contains: a brief source " + "yields a single short paragraph; a detailed source yields two to three full paragraphs. Never " + "pad a thin section to reach a length, and never compress a detailed one into a single " + "paragraph — a long source deserves a correspondingly long summary.\n" + "- Each value must be plain text (no markdown, no bullet lists, no nested JSON): one or more " + "well-structured paragraphs in professional humanitarian language, separated by a blank line, " + "or an empty string when that section has no data.\n" "- Return ONLY the JSON object, with no surrounding prose or code fences." ) @@ -138,6 +144,9 @@ def _extract_fields(obj, field_names: List[str]) -> dict: SITUATIONAL_COMMON_FIELDS: List[str] = ["event_description", "event_scope"] +# Imminent DREF applications created on the v2 use hazard_date_and_location. +IMMINENT_SITUATIONAL_FIELDS: List[str] = ["hazard_date_and_location"] + OPERATIONAL_COMMON_FIELDS: List[str] = ["operation_objective", "response_strategy"] PEOPLE_COMMON_FIELDS: List[str] = ["people_assisted", "selection_criteria"] @@ -151,14 +160,10 @@ def __init__(self): @staticmethod def _situational_overview_kwargs(source_doc) -> dict: - """Build situational_overview kwargs — common across all document types. - - ``event_scope`` is one of the common fields; when it is empty (e.g. an - Imminent DREF Application where the scope is not yet known) - ``_extract_fields`` drops it automatically, while by the Final Report - stage the event has materialized and the field feeds the summary. - """ - return _extract_fields(source_doc, SITUATIONAL_COMMON_FIELDS) + """Imminent v2 applications describe the situation in the scenario analysis fields; others use the common ones.""" + if isinstance(source_doc, Dref) and source_doc.type_of_dref == Dref.DrefType.IMMINENT and source_doc.is_dref_imminent_v2: + return _extract_fields(source_doc, IMMINENT_SITUATIONAL_FIELDS) + return _extract_fields(source_doc, SITUATIONAL_COMMON_FIELDS) # event_scope is empty for Assessment; dropped @staticmethod def _challenges_and_lessons_kwargs(source_doc) -> Dict[str, dict]: @@ -345,8 +350,5 @@ def generate_all(self, source_doc: DrefSummarySource, section_kwargs: Optional[D value = parsed.get(field_name) if not isinstance(value, str): continue - summary = value.strip() - if len(summary) > MAX_OUTPUT_CHARS_PER_FIELD: - summary = summary[:MAX_OUTPUT_CHARS_PER_FIELD].rstrip() - results[field_name] = summary + results[field_name] = value.strip() return results From 98d136c05bece492d423c39b07938fd3019c2898 Mon Sep 17 00:00:00 2001 From: sandeshit Date: Mon, 3 Aug 2026 15:00:59 +0545 Subject: [PATCH 2/5] feat(llm): alter the api version for azure-openai. --- main/llm.py | 2 +- main/settings.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/main/llm.py b/main/llm.py index 46860e10a..5c3b82ae4 100644 --- a/main/llm.py +++ b/main/llm.py @@ -55,7 +55,7 @@ def client(self): return AzureOpenAI( azure_endpoint=settings.AZURE_OPENAI_ENDPOINT, api_key=settings.AZURE_OPENAI_API_KEY, - api_version="2023-05-15", + api_version=settings.AZURE_OPENAI_API_VERSION, ) def get_response(self, messages: Messages) -> Optional[str]: diff --git a/main/settings.py b/main/settings.py index b7b36d5f7..0f8c4d1c9 100644 --- a/main/settings.py +++ b/main/settings.py @@ -148,6 +148,9 @@ AZURE_OPENAI_ENDPOINT=(str, None), AZURE_OPENAI_API_KEY=(str, None), AZURE_OPENAI_DEPLOYMENT_NAME=(str, None), + # Azure OpenAI REST api-version. Keep this on a GA (non-preview) release; a + # deployment can be pinned back to an older one without a code change. + AZURE_OPENAI_API_VERSION=(str, "2024-10-21"), # Use a fake LLM client instead of calling Azure OpenAI USE_DUMMY_LLM_CLIENT=(bool, False), # ReliefWeb appname @@ -907,6 +910,7 @@ def decode_base64(env_key, fallback_env_key): AZURE_OPENAI_ENDPOINT = env("AZURE_OPENAI_ENDPOINT") AZURE_OPENAI_API_KEY = env("AZURE_OPENAI_API_KEY") AZURE_OPENAI_DEPLOYMENT_NAME = env("AZURE_OPENAI_DEPLOYMENT_NAME") +AZURE_OPENAI_API_VERSION = env("AZURE_OPENAI_API_VERSION") USE_DUMMY_LLM_CLIENT = env("USE_DUMMY_LLM_CLIENT") OIDC_ENABLE = env("OIDC_ENABLE") From 77c280cd6a4d3e773a07eff772cafd2fdefc843b Mon Sep 17 00:00:00 2001 From: sandeshit Date: Wed, 5 Aug 2026 14:04:21 +0545 Subject: [PATCH 3/5] fixup! feat(llm): alter the api version for azure-openai. --- dref/summary.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dref/summary.py b/dref/summary.py index 73c18133d..9dbe88940 100644 --- a/dref/summary.py +++ b/dref/summary.py @@ -110,8 +110,7 @@ def _build_lessons_learned_prompt(**kwargs) -> str: "- Preserve every specific figure, location and timeframe from the source; never fabricate them.\n" "- If a section's data block is empty or holds no usable content, set that key to an empty " "string. Never write a sentence stating that data is missing, not provided or not recorded.\n" - "- Scale each section's length to how much its source data actually contains: a brief source " - "yields a single short paragraph; a detailed source yields two to three full paragraphs. Never " + "- Scale each section's length to how much its source data actually contains. Never " "pad a thin section to reach a length, and never compress a detailed one into a single " "paragraph — a long source deserves a correspondingly long summary.\n" "- Each value must be plain text (no markdown, no bullet lists, no nested JSON): one or more " From 6a4ca6b62ca60ef2b4180ff9fc8008a3a49bbe3b Mon Sep 17 00:00:00 2001 From: sandeshit Date: Tue, 11 Aug 2026 14:49:29 +0545 Subject: [PATCH 4/5] fixup! feat(llm): alter the api version for azure-openai. --- dref/summary.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/dref/summary.py b/dref/summary.py index 9dbe88940..49978c89d 100644 --- a/dref/summary.py +++ b/dref/summary.py @@ -40,8 +40,9 @@ SYSTEM_MESSAGE = ( "You are an IFRC expert analyst specializing in DREF (Disaster Response Emergency Fund) " - "operations. Analyze the provided DREF data and produce clear, professional humanitarian " - "summaries suitable for IFRC staff and National Society personnel. Use only the information " + "operations. You write short executive summaries for IFRC staff and National Society personnel " + "who will read the full document separately, so your task is to condense and synthesise, never " + "to restate the source. Use only the information " "provided in the data; do not invent facts, figures, or details. Where supporting information " "for a section is absent, return an empty string for that section rather than speculating." ) @@ -90,8 +91,8 @@ def _build_lessons_learned_prompt(**kwargs) -> str: GLOBAL_PROMPT = ( "The DREF data above is organised by summary section. Using ONLY that data, write five " - "summary sections. Return a single JSON object (and nothing else) with exactly these keys, each " - "summarising the block of the same name:\n" + "condensed summary sections. Return a single JSON object (and nothing else) with exactly these " + "keys, each summarising the block of the same name:\n" "\n" ' "situational_overview": The disaster situation and the rationale for the operation. Use the ' 'data under the "situational_overview" key.\n' @@ -107,13 +108,17 @@ def _build_lessons_learned_prompt(**kwargs) -> str: "Requirements:\n" "- Summarise only what each section's data actually contains; do not add topics or details it " "does not mention.\n" - "- Preserve every specific figure, location and timeframe from the source; never fabricate them.\n" + "- Synthesise, do not concatenate: group related points into a coherent narrative instead of " + "restating the source line by line or field by field. Merge repeated or overlapping points into " + "a single statement.\n" + "- Each section is at most three paragraphs, whatever the length of its source data. A thin " + "source should yield a single paragraph; a rich one may use the full three, but no source " + "justifies more.\n" + "- Open each section with its single most important point, then add supporting context.\n" + "- Preserve important facts and figures exactly as given; never invent or alter them.\n" "- If a section's data block is empty or holds no usable content, set that key to an empty " "string. Never write a sentence stating that data is missing, not provided or not recorded.\n" - "- Scale each section's length to how much its source data actually contains. Never " - "pad a thin section to reach a length, and never compress a detailed one into a single " - "paragraph — a long source deserves a correspondingly long summary.\n" - "- Each value must be plain text (no markdown, no bullet lists, no nested JSON): one or more " + "- Each value must be plain text (no markdown, no bullet lists, no nested JSON): one to three " "well-structured paragraphs in professional humanitarian language, separated by a blank line, " "or an empty string when that section has no data.\n" "- Return ONLY the JSON object, with no surrounding prose or code fences." From 7bf86d36c6560da06d5aa71adeb631205a3d048b Mon Sep 17 00:00:00 2001 From: sandeshit Date: Wed, 12 Aug 2026 22:43:08 +0545 Subject: [PATCH 5/5] chore(helm): add new configmap value --- deploy/helm/ifrcgo-helm/values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/helm/ifrcgo-helm/values.yaml b/deploy/helm/ifrcgo-helm/values.yaml index f4f3f072f..53ed90549 100644 --- a/deploy/helm/ifrcgo-helm/values.yaml +++ b/deploy/helm/ifrcgo-helm/values.yaml @@ -71,6 +71,8 @@ env: RELIEF_WEB_APP_NAME: '' POWERBI_WORKSPACE_ID: '' POWERBI_DATASET_IDS: '' + EOAPI_STAC_EXTERNAL_URL: '' + EOAPI_STAC_INTERNAL_URL: '' # NOTE: Used to pass additional configs to api/worker containers # NOTE: Not used by azure vault