diff --git a/frontend/src/components/chat/FundusRecordCard.tsx b/frontend/src/components/chat/FundusRecordCard.tsx index dadb00d..561b6d8 100644 --- a/frontend/src/components/chat/FundusRecordCard.tsx +++ b/frontend/src/components/chat/FundusRecordCard.tsx @@ -17,7 +17,7 @@ import { import Grid from "@mui/material/Grid2"; import React, { useEffect, useState } from "react"; import { useAgentService } from "../../hooks/useLookupService"; -import { FundusRecord, FundusRecordImage } from "../../types/fundusTypes"; +import { FundusCollection, FundusRecord, FundusRecordImage } from "../../types/fundusTypes"; interface FundusRecordCardProps { muragId: string; @@ -26,11 +26,12 @@ interface FundusRecordCardProps { const FundusRecordCard: React.FC = ({ muragId }) => { const [record, setRecord] = useState(undefined); const [image, setImage] = useState(undefined); + const [collection, setCollection] = useState(undefined); const [loading, setLoading] = useState(true); const [modalOpen, setModalOpen] = useState(false); const [imageLoaded, setImageLoaded] = useState(false); const [elevation, setElevation] = useState(1); - const { getFundusRecord, getFundusRecordImage } = useAgentService(); + const { getFundusRecord, getFundusRecordImage, getFundusCollectionByName } = useAgentService(); useEffect(() => { const fetchRecordData = async () => { @@ -42,6 +43,11 @@ const FundusRecordCard: React.FC = ({ muragId }) => { if (imageData) { setImage(imageData); } + // Fetch collection to get field ordering information + const collectionData = await getFundusCollectionByName(recordData.collection_name); + if (collectionData) { + setCollection(collectionData); + } } } catch (error) { console.error("Error fetching record:", error); @@ -51,7 +57,7 @@ const FundusRecordCard: React.FC = ({ muragId }) => { }; fetchRecordData(); - }, [muragId, getFundusRecord, getFundusRecordImage]); + }, [muragId, getFundusRecord, getFundusRecordImage, getFundusCollectionByName]); const handleOpenModal = () => { setModalOpen(true); @@ -170,11 +176,32 @@ const FundusRecordCard: React.FC = ({ muragId }) => { Additional Details - {Object.entries(record.details).map(([key, value]) => ( - - {key}: {value} - - ))} + {(() => { + // Order details based on position from collection field configuration + const detailEntries = Object.entries(record.details); + + if (collection && collection.fields) { + // Create a map of field labels to positions + const fieldPositions = new Map( + collection.fields + .filter((f): f is FundusRecordField & { position: number } => f.position !== null) + .map(f => [f.label_en, f.position]) + ); + + // Sort details by position + detailEntries.sort((a, b) => { + const posA = fieldPositions.get(a[0]) ?? 999; + const posB = fieldPositions.get(b[0]) ?? 999; + return posA - posB; + }); + } + + return detailEntries.map(([key, value]) => ( + + {key}: {value} + + )); + })()} diff --git a/frontend/src/hooks/useLookupService.ts b/frontend/src/hooks/useLookupService.ts index f825aa0..7bcdab4 100644 --- a/frontend/src/hooks/useLookupService.ts +++ b/frontend/src/hooks/useLookupService.ts @@ -49,5 +49,19 @@ export function useAgentService() { } }, []); - return { loading, error, getFundusRecord, getFundusRecordImage, getFundusCollection }; + const getFundusCollectionByName = useCallback(async (collectionName: string): Promise => { + setLoading(true); + setError(null); + try { + const collection = await lookupService.getFundusCollectionByName(collectionName); + return collection; + } catch (err) { + setError(err as Error); + return undefined; + } finally { + setLoading(false); + } + }, []); + + return { loading, error, getFundusRecord, getFundusRecordImage, getFundusCollection, getFundusCollectionByName }; } diff --git a/frontend/src/services/lookupApiService.ts b/frontend/src/services/lookupApiService.ts index cbd53ba..033f03b 100644 --- a/frontend/src/services/lookupApiService.ts +++ b/frontend/src/services/lookupApiService.ts @@ -21,6 +21,14 @@ export const lookupService = { throw new Error('Failed to fetch Fundus Collection'); } return response.json(); + }, + async getFundusCollectionByName(collectionName: string): Promise { + const params = new URLSearchParams({ collection_name: collectionName }); + const response = await fetch(`/api/data/lookup/collections?${params}`); + if (!response.ok) { + throw new Error('Failed to fetch Fundus Collection'); + } + return response.json(); } }; diff --git a/frontend/src/types/fundusTypes.ts b/frontend/src/types/fundusTypes.ts index fe344d9..7cb8b3b 100644 --- a/frontend/src/types/fundusTypes.ts +++ b/frontend/src/types/fundusTypes.ts @@ -18,6 +18,7 @@ export interface FundusRecordField { name: string; label_en: string; label_de: string; + position: number | null; } export interface FundusCollection { diff --git a/src/fundus_murag/data/dtos/fundus.py b/src/fundus_murag/data/dtos/fundus.py index bee9537..0d3cbbe 100644 --- a/src/fundus_murag/data/dtos/fundus.py +++ b/src/fundus_murag/data/dtos/fundus.py @@ -38,11 +38,13 @@ class FundusRecordField(BaseModel): name (str): The name of the field. label_en (str): The label of the field in English. label_de (str): The label of the field in German. + position (int | None): The position/order of the field for display purposes. """ name: str label_en: str label_de: str + position: int | None = None class FundusCollection(BaseModel): diff --git a/src/fundus_murag/data/vector_db.py b/src/fundus_murag/data/vector_db.py index a13c7b4..82ad235 100644 --- a/src/fundus_murag/data/vector_db.py +++ b/src/fundus_murag/data/vector_db.py @@ -290,12 +290,11 @@ def _resolve_detail_field_names(self, details: list[dict[str, str]], collection_ if detail["value"] == "None" or detail["value"] == "": continue field_value = detail["value"] - if detail["key"] == "ident_nr" or detail["key"] not in fields: - field_name = detail["key"] - else: + # Only include fields that are configured in the collection's fields + # This ensures only visible fields are shown + if detail["key"] in fields: field_name = fields[detail["key"]] - - resolved[field_name] = field_value + resolved[field_name] = field_value return resolved diff --git a/src/fundus_murag/scripts/generate_murag_data.py b/src/fundus_murag/scripts/generate_murag_data.py index dded7d1..1dfddc3 100644 --- a/src/fundus_murag/scripts/generate_murag_data.py +++ b/src/fundus_murag/scripts/generate_murag_data.py @@ -311,6 +311,7 @@ def _add_collection_fields(fields_dir: Path, collections_df: pd.DataFrame) -> pd "field_name": [], "csv_headers": [], "labels": [], + "positions": [], "title_fields": [], } for k, v in fields_dfs.items(): @@ -328,6 +329,12 @@ def _add_collection_fields(fields_dir: Path, collections_df: pd.DataFrame) -> pd data["labels"].append( [{"de": ld, "en": le} for ld, le in zip(list(v["label_de"]), list(v["label_en"]))] ) + + # Extract position field if it exists + if "position" in list(v.columns): + data["positions"].append(v["position"].values) + else: + data["positions"].append([None] * len(v)) title_fields = {} for _, row in v.iterrows(): @@ -359,9 +366,11 @@ def _add_collection_fields(fields_dir: Path, collections_df: pd.DataFrame) -> pd fns = row["field_name"] labels = row["labels"] + positions = row["positions"] fields = [] - for fn, label in zip(fns, labels): + for fn, label, pos in zip(fns, labels, positions): ld, le = label["de"], label["en"] + # Skip fields without labels (they should not be displayed) if (ld is None or ld == "") and (le is None or le == ""): continue fields.append( @@ -369,6 +378,7 @@ def _add_collection_fields(fields_dir: Path, collections_df: pd.DataFrame) -> pd "name": fn, "label_de": ld, "label_en": le, + "position": int(pos) if pos is not None and not pd.isna(pos) else None, } ) data["fields"].append(fields) @@ -478,6 +488,24 @@ def _create_records_df( axis=1, ).drop("details", axis=1) + # Filter details columns to only include fields visible for each collection + def filter_details_for_collection(row): + collection_name = row["collection_name"] + collection = collections_df[collections_df.collection_name == collection_name].iloc[0] + visible_field_names = {field["name"] for field in collection.fields} + + # Create a new row with only visible detail fields + filtered_row = {k: v for k, v in row.items() if not k.startswith("details_")} + for k, v in row.items(): + if k.startswith("details_"): + field_name = k.replace("details_", "") + if field_name in visible_field_names: + filtered_row[k] = v + + return pd.Series(filtered_row) + + records_df = records_df.apply(filter_details_for_collection, axis=1) + # Add title records_df["title"] = records_df.apply(lambda x: _get_record_title(x, collections_df), axis=1) title = records_df.pop("title")