From 8b9cdcc62d6bcef0fdf62d7d2ab59ba286d27319 Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Wed, 3 Jul 2024 13:36:52 +0200 Subject: [PATCH] feat: add sounds and confetti for more snazziness add: new levels chore: clean-up the ai-generated code a bit --- bun.lockb | Bin 143608 -> 144404 bytes package.json | 1 + src/App.tsx | 2 +- src/components/ConfettiCelebration.tsx | 35 ++ src/components/HealthBar.tsx | 20 ++ src/components/openEHRQuest.tsx | 256 ++++++++++++++ src/hooks/useSoundEffects.ts | 38 +++ src/lib/levels.ts | 425 ++++++++++++++++++++++++ src/openEHRQuest.tsx | 443 ------------------------- 9 files changed, 776 insertions(+), 444 deletions(-) create mode 100644 src/components/ConfettiCelebration.tsx create mode 100644 src/components/HealthBar.tsx create mode 100644 src/components/openEHRQuest.tsx create mode 100644 src/hooks/useSoundEffects.ts create mode 100644 src/lib/levels.ts delete mode 100644 src/openEHRQuest.tsx diff --git a/bun.lockb b/bun.lockb index db80d7de7d1706444ddd62f8b2fd6a414dfc5d67..a82e8dd2334903e78d9784b4736d1a012ffda7cc 100755 GIT binary patch delta 24816 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 delta 24335 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 { + const [windowDimension, setWindowDimension] = useState({ + width: window.innerWidth, + height: window.innerHeight, + }); + + useEffect(() => { + const handleResize = () => { + setWindowDimension({ + width: window.innerWidth, + height: window.innerHeight, + }); + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return ( + + ); +}; + +export default ConfettiCelebration; diff --git a/src/components/HealthBar.tsx b/src/components/HealthBar.tsx new file mode 100644 index 0000000..80855d4 --- /dev/null +++ b/src/components/HealthBar.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +interface HealthBarProps { + health: number; +} + +const HealthBar: React.FC = ({ health }) => { + return ( +
+
+
+
+
+ ); +}; + +export default HealthBar; diff --git a/src/components/openEHRQuest.tsx b/src/components/openEHRQuest.tsx new file mode 100644 index 0000000..1a78ee1 --- /dev/null +++ b/src/components/openEHRQuest.tsx @@ -0,0 +1,256 @@ +import React, { useState, useEffect } from 'react'; +import { Terminal, Brain, Shield, Zap, VolumeX, Volume2 } from 'lucide-react'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Button } from '@/components/ui/button'; +import { + Card, + CardHeader, + CardContent, + CardFooter, +} from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { Badge } from '@/components/ui/badge'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import HealthBar from '@/components/HealthBar'; +import useSoundEffects from '@/hooks/useSoundEffects.ts'; +import ConfettiCelebration from '@/components/ConfettiCelebration'; +import { levels as levelsData } from '@/lib/levels.ts'; + +interface GameState { + currentLevel: number; + score: number; + playerHealth: number; + badges: string[]; + hintUsed: boolean; +} + +const OpenEHRQuest: React.FC = () => { + const [gameState, setGameState] = useState({ + currentLevel: 0, + score: 0, + playerHealth: 100, + badges: [], + hintUsed: false, + }); + const [soundEnabled, setSoundEnabled] = useState(true); + const { playCorrectSound, playWrongSound, playCompletionSound } = + useSoundEffects(); + + const levels = levelsData; + + useEffect(() => { + if ( + gameState.currentLevel >= levels.length && + levels.length > 0 && + soundEnabled + ) { + playCompletionSound(); + } + }, [ + gameState.currentLevel, + levels.length, + soundEnabled, + playCompletionSound, + ]); + + const handleAnswer = (selectedIndex: number) => { + const currentLevel = levels[gameState.currentLevel]; + if (selectedIndex === currentLevel.correctAnswer) { + if (soundEnabled) playCorrectSound(); + setGameState((prevState) => ({ + ...prevState, + score: prevState.score + (prevState.hintUsed ? 50 : 100), + currentLevel: prevState.currentLevel + 1, + hintUsed: false, + badges: [ + ...prevState.badges, + `Level ${prevState.currentLevel + 1} Master`, + ], + })); + } else { + if (soundEnabled) playWrongSound(); + setGameState((prevState) => ({ + ...prevState, + playerHealth: Math.max(0, prevState.playerHealth - 20), + hintUsed: false, + })); + } + }; + + const useHint = () => { + setGameState((prevState) => ({ ...prevState, hintUsed: true })); + }; + + const resetGame = () => { + setGameState({ + currentLevel: 0, + score: 0, + playerHealth: 100, + badges: [], + hintUsed: false, + }); + }; + + const toggleSound = () => { + setSoundEnabled(!soundEnabled); + }; + + if (gameState.playerHealth <= 0) { + return ( +
+ + +

Game Over

+
+ +

+ Your OpenEHR journey has come to an end. +

+

Final Score: {gameState.score}

+
+ + + +
+
+ ); + } + + if (gameState.currentLevel >= levels.length && levels.length > 0) { + return ( +
+ + + +

+ 🎉 Congratulations! 🎉 +

+
+ +

+ You've become an OpenEHR Integration Master! +

+

Final Score: {gameState.score}

+
+ {gameState.badges.map((badge, index) => ( + + {badge} + + ))} +
+
+ + + +
+
+ ); + } + const currentLevel = levels[gameState.currentLevel]; + + return ( +
+ + +

{currentLevel.title}

+ +
+ +

+ Level {gameState.currentLevel + 1} of {levels.length} +

+ + + + Mission Briefing + {currentLevel.description} + +
+

Challenge:

+
+              {currentLevel.challenge}
+            
+
+
+ {currentLevel.options.map((option, index) => ( + + ))} +
+
+
+ + + + + + +

+ {gameState.hintUsed + ? 'Hint already used' + : 'Click to reveal a hint (reduces points for this level)'} +

+
+
+
+
+ {gameState.badges.slice(-3).map((badge, index) => ( + + {badge} + + ))} +
+
+ {gameState.hintUsed && ( + + Hint + {currentLevel.hint} + + )} +
+
+ +
+
+ + Score: {gameState.score} +
+
+ + Health: {gameState.playerHealth}% +
+
+ +
+
+
+ ); +}; + +export default OpenEHRQuest; diff --git a/src/hooks/useSoundEffects.ts b/src/hooks/useSoundEffects.ts new file mode 100644 index 0000000..798d7b5 --- /dev/null +++ b/src/hooks/useSoundEffects.ts @@ -0,0 +1,38 @@ +import { useCallback } from 'react'; + +const useSoundEffects = () => { + const playSound = useCallback((frequency: number, duration: number) => { + const audioContext = new (window.AudioContext || + (window as any).webkitAudioContext)(); + const oscillator = audioContext.createOscillator(); + const gainNode = audioContext.createGain(); + + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + + oscillator.frequency.value = frequency; + oscillator.type = 'sine'; + + gainNode.gain.setValueAtTime(0, audioContext.currentTime); + gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 0.01); + gainNode.gain.linearRampToValueAtTime( + 0, + audioContext.currentTime + duration + ); + + oscillator.start(audioContext.currentTime); + oscillator.stop(audioContext.currentTime + duration); + }, []); + + const playCorrectSound = useCallback(() => playSound(800, 0.1), [playSound]); + const playWrongSound = useCallback(() => playSound(300, 0.2), [playSound]); + const playCompletionSound = useCallback(() => { + playSound(523.25, 0.1); // C5 + setTimeout(() => playSound(659.25, 0.1), 100); // E5 + setTimeout(() => playSound(783.99, 0.2), 200); // G5 + }, [playSound]); + + return { playCorrectSound, playWrongSound, playCompletionSound }; +}; + +export default useSoundEffects; diff --git a/src/lib/levels.ts b/src/lib/levels.ts new file mode 100644 index 0000000..2ee30b3 --- /dev/null +++ b/src/lib/levels.ts @@ -0,0 +1,425 @@ +export interface Level { + title: string; + description: string; + challenge: string; + options: string[]; + correctAnswer: number; + explanation: string; + hint: string; +} + +export const levels: Level[] = [ + // Existing levels (reordered and slightly modified for difficulty progression) + { + title: 'The Template Tavern', + description: + 'Welcome to The Template Tavern! Your first quest is to decipher a mysterious web template. Choose the correct interpretation to proceed.', + challenge: + "You encounter a web template with the following structure:\n\n\n \n \n \n \n \n \n \n 120\n mm[Hg]\n \n \n \n \n \n \n \n", + options: [ + "It's a recipe for a health potion", + "It's a map to the hidden treasure of OpenEHR", + "It's a blood pressure measurement of 120 mm[Hg]", + "It's the secret code to unlock the next dungeon", + ], + correctAnswer: 2, + explanation: + "This template represents a blood pressure measurement. The 'observation' element with the archetype ID 'openEHR-EHR-OBSERVATION.blood_pressure.v1' indicates it's for blood pressure, and the value 120 mm[Hg] is clearly visible in the structure.", + hint: 'Look for the archetype ID and the value within the structure.', + }, + { + title: 'The Archetype Archives', + description: + "You've entered the Archetype Archives. Decipher the ancient scrolls to progress!", + challenge: 'What is the primary purpose of archetypes in OpenEHR?', + options: [ + 'To store patient data', + 'To define reusable clinical concepts', + 'To create user interfaces', + 'To manage database connections', + ], + correctAnswer: 1, + explanation: + 'Archetypes in OpenEHR are used to define reusable clinical concepts. They provide a way to model clinical information that can be shared across different systems and contexts.', + hint: 'Think about the role of archetypes in standardizing clinical data.', + }, + { + title: 'The Composition Cauldron', + description: + 'Welcome to the Composition Cauldron, where data elements are brewed into meaningful records!', + challenge: 'In OpenEHR, what is a composition primarily used for?', + options: [ + 'To define database schemas', + 'To create user interfaces', + 'To represent a clinical document or event', + 'To manage system security', + ], + correctAnswer: 2, + explanation: + "In OpenEHR, a composition is used to represent a clinical document or event. It's the top-level structure that contains all the clinical data for a single record, such as a consultation, a test result, or a discharge summary.", + hint: 'Consider what might represent a complete, standalone clinical record.', + }, + { + title: 'The Composition Composer', + description: + 'The Composition Composer challenges you to orchestrate the elements of an OpenEHR composition!', + challenge: + 'Which of the following is NOT typically found at the root level of an OpenEHR composition?', + options: ['context', 'category', 'content', 'observation'], + correctAnswer: 3, + explanation: + "In an OpenEHR composition, 'observation' is not typically found at the root level. The root level usually includes 'context', 'category', and 'content'. Observations and other clinical entry types are usually nested within the 'content' section.", + hint: 'Think about the high-level structure of a composition and what elements define its overall properties.', + }, + { + title: 'The Integration Inquisitor', + description: + 'The Integration Inquisitor will test your knowledge of posting compositions to an OpenEHR system.', + challenge: + 'Which HTTP method and content type should you use when posting a composition to an OpenEHR system?', + options: [ + 'GET request with application/json content type', + 'POST request with application/xml content type', + 'PUT request with text/plain content type', + 'POST request with application/json content type', + ], + correctAnswer: 3, + explanation: + "When posting a new composition to an OpenEHR system, you typically use a POST request with application/json content type. This allows you to send structured data in a format that's widely supported and easy to work with.", + hint: 'Think about which HTTP method is typically used for creating new resources.', + }, + { + title: 'The Versioning Vault', + description: + 'Welcome to the Versioning Vault, where the history of health records is preserved through the ages!', + challenge: + 'When retrieving a specific version of a composition, which of the following is true?', + options: [ + 'You must always retrieve the latest version', + 'You can specify a version number or time to get a specific historical version', + 'Versioning is not supported in OpenEHR', + 'You need to retrieve all versions and manually find the one you want', + ], + correctAnswer: 1, + explanation: + 'OpenEHR supports versioning of compositions. You can retrieve a specific version by specifying either a version number or a timestamp, allowing access to the historical state of a composition at a particular point in time.', + hint: 'Think about how you might want to access historical data in a medical record system.', + }, + { + title: 'The Constraint Connoisseur', + description: + 'The Constraint Connoisseur tests your ability to understand and apply constraints in OpenEHR archetypes!', + challenge: + 'Which of the following is a valid way to constrain a quantity data type in an archetype?', + options: [ + 'Setting a specific value that must be used', + 'Defining a range of allowed values', + 'Specifying a list of allowed units', + 'All of the above', + ], + correctAnswer: 3, + explanation: + 'In OpenEHR archetypes, quantity data types can be constrained in multiple ways. You can set a specific value, define a range of allowed values, or specify a list of allowed units. Often, a combination of these constraints is used to precisely define the acceptable values for a quantity.', + hint: 'Consider the various ways you might want to limit or specify how a quantity can be recorded in a clinical context.', + }, + { + title: 'The Binding Bard', + description: + 'The Binding Bard challenges you to understand the intricacies of binding terminologies in OpenEHR!', + challenge: + 'Which method is used to bind external terminologies to archetype nodes?', + options: [ + 'Direct insertion of codes into archetypes', + "Using the 'term_bindings' section in archetypes", + 'Creating separate terminology archetypes', + 'Modifying the reference model to include terminologies', + ], + correctAnswer: 1, + explanation: + "OpenEHR uses the 'term_bindings' section in archetypes to bind external terminologies. This allows for flexible integration of various coding systems while maintaining the archetype structure.", + hint: 'Think about how OpenEHR maintains separation between its structure and external vocabularies.', + }, + { + title: 'The Operational Template Oracle', + description: + 'The Operational Template Oracle tests your knowledge of OPTs in the OpenEHR ecosystem!', + challenge: + 'What is the primary purpose of an Operational Template (OPT) in OpenEHR?', + options: [ + 'To replace archetypes in modern OpenEHR implementations', + 'To provide a fully specialized and flattened form of a template for direct use in systems', + 'To create new archetypes based on existing ones', + 'To define the operational procedures in a healthcare setting', + ], + correctAnswer: 1, + explanation: + "An Operational Template (OPT) in OpenEHR is a fully specialized and flattened form of a template. It's designed for direct use in systems, containing all the necessary information from archetypes and templates in a readily usable format.", + hint: 'Consider what form of templates would be most immediately usable by EHR systems.', + }, + { + title: 'The Persistence Pilgrim', + description: + 'The Persistence Pilgrim challenges your understanding of data storage in OpenEHR systems!', + challenge: + 'Which statement best describes how data is typically stored in an OpenEHR system?', + options: [ + 'Data is stored in relational tables matching the structure of archetypes', + 'Each composition is stored as a single document in a NoSQL database', + 'Data is stored in a node + path + value format, preserving the hierarchical structure', + 'Archetypes themselves store the data values directly', + ], + correctAnswer: 2, + explanation: + 'In many OpenEHR implementations, data is typically stored in a node + path + value format. This approach preserves the hierarchical structure defined by archetypes and templates, while allowing for efficient storage and querying of the data.', + hint: 'Think about a storage method that would allow for flexible querying while maintaining the structure defined by archetypes.', + }, + { + title: 'The Composition Conjurer', + description: + "You've made it to the Composition Conjurer's lair! Your task is to craft a composition that will impress the Conjurer.", + challenge: + "Create a composition for a patient's body temperature. The archetype ID is 'openEHR-EHR-OBSERVATION.body_temperature.v2'. The temperature is 37.5°C.", + options: [ + `{ + "composition": { + "content": [ + { + "observation": { + "archetype_id": "openEHR-EHR-OBSERVATION.body_temperature.v2", + "data": { + "events": [ + { + "data": { + "items": [ + { + "value": { + "magnitude": 37.5, + "units": "°C" + } + } + ] + } + } + ] + } + } + } + ] + } +}`, + `{ + "patient": { + "vitals": { + "temperature": 37.5, + "unit": "celsius" + } + } +}`, + `{ + "openEHR": { + "body_temperature": { + "value": 37.5, + "unit": "C" + } + } +}`, + `{ + "composition": { + "archetype": "openEHR-EHR-OBSERVATION.body_temperature.v2", + "temperature": "37.5°C" + } +}`, + ], + correctAnswer: 0, + explanation: + 'The correct composition follows the OpenEHR structure, including the proper archetype ID, and nests the temperature value within the expected data structure.', + hint: 'Consider the hierarchical structure of OpenEHR compositions and how data is typically represented.', + }, + { + title: 'The AQL Alchemist', + description: + 'Enter the laboratory of the AQL Alchemist, where queries are brewed to extract the essence of clinical data!', + challenge: + "Which AQL query will retrieve all blood pressure measurements for a specific patient with EHR ID 'abc123'?", + options: [ + "SELECT o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude AS systolic, o/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude AS diastolic FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v1] WHERE e/ehr_id/value='abc123'", + "SELECT * FROM EHR WHERE patient_id = 'abc123' AND observation_type = 'blood_pressure'", + "GET blood_pressure FROM patient WHERE id = 'abc123'", + "FIND 'blood pressure' IN EHR 'abc123'", + ], + correctAnswer: 0, + explanation: + 'This AQL query correctly navigates the openEHR structure to retrieve systolic and diastolic blood pressure values from the specific archetype, filtering for the given EHR ID.', + hint: 'AQL uses a structure similar to SQL, but with paths that navigate the openEHR data model.', + }, + // New levels (added for increased difficulty and broader coverage) + { + title: 'The Template Transformer', + description: + 'The Template Transformer challenges you to understand the relationship between archetypes and templates!', + challenge: 'How do templates differ from archetypes in OpenEHR?', + options: [ + 'Templates are used to create archetypes', + 'Templates combine and constrain archetypes for specific use cases', + 'Templates and archetypes are interchangeable terms', + 'Templates define the basic clinical concepts, while archetypes specialize them', + ], + correctAnswer: 1, + explanation: + 'In OpenEHR, templates are used to combine and constrain archetypes for specific use cases. They allow for the creation of purpose-specific data structures by selecting relevant parts of archetypes and applying additional constraints.', + hint: 'Think about how you might need to adapt general clinical concepts for specific scenarios or forms.', + }, + { + title: 'The Demographic Detective', + description: + 'The Demographic Detective challenges you to understand how patient information is structured in OpenEHR!', + challenge: + 'Which of the following is true about demographic information in OpenEHR?', + options: [ + 'Demographic data is stored directly in compositions', + 'OpenEHR uses a separate demographic model', + 'Demographic data is always part of the EHR', + "OpenEHR doesn't support storing demographic information", + ], + correctAnswer: 1, + explanation: + 'OpenEHR uses a separate demographic model to manage patient, practitioner, and organization information. This separation allows for more flexible and privacy-conscious handling of demographic data.', + hint: 'Consider how separating certain types of data might benefit data management and privacy.', + }, + { + title: 'The Terminology Tamer', + description: + 'The Terminology Tamer tests your knowledge of integrating external terminologies with OpenEHR!', + challenge: + 'How does OpenEHR integrate with external terminology systems like SNOMED CT?', + options: [ + "It doesn't - OpenEHR uses its own terminology exclusively", + 'External terminologies replace OpenEHR archetypes', + 'Through terminology bindings in archetypes and templates', + 'By converting all external terms into OpenEHR-specific codes', + ], + correctAnswer: 2, + explanation: + 'OpenEHR integrates with external terminology systems like SNOMED CT through terminology bindings in archetypes and templates. This allows for the use of standardized codes and terms within the OpenEHR structure.', + hint: 'Think about how OpenEHR might maintain its structure while still leveraging standardized medical terminologies.', + }, + { + title: 'The Interoperability Innovator', + description: + 'The Interoperability Innovator challenges you to think about how OpenEHR facilitates data exchange between different systems!', + challenge: + 'Which feature of OpenEHR primarily contributes to its interoperability capabilities?', + options: [ + 'Its use of a proprietary data format', + 'The separation of information models from clinical models', + 'Its reliance on a single, universal database system', + 'The requirement for all systems to use the same software', + ], + correctAnswer: 1, + explanation: + "OpenEHR's separation of information models (reference model) from clinical models (archetypes and templates) is a key feature that contributes to its interoperability. This separation allows for shared understanding of clinical concepts across different systems and contexts.", + hint: 'Consider how separating different aspects of the model might allow for flexibility and shared understanding.', + }, + { + title: 'The Query Quandary', + description: + 'The Query Quandary tests your ability to construct complex AQL queries for real-world scenarios!', + challenge: + 'Which AQL query would correctly retrieve the latest blood pressure readings for all patients diagnosed with hypertension in the last year?', + options: [ + 'SELECT e/ehr_id/value, o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude AS systolic, o/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude AS diastolic FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v1] WHERE c/context/start_time > current_date - P1Y ORDER BY c/context/start_time DESC', + "SELECT * FROM EHR WHERE diagnosis = 'hypertension' AND diagnosis_date > current_date - 365 ORDER BY observation_date DESC LIMIT 1", + "GET latest_blood_pressure FROM patients WHERE diagnosis = 'hypertension' AND diagnosis_date > now() - interval '1 year'", + "FIND blood_pressure IN EHR WHERE condition = 'hypertension' AND condition.date > 1 year ago ORDER BY date DESC", + ], + correctAnswer: 0, + explanation: + 'This AQL query correctly navigates the openEHR structure to retrieve the latest blood pressure readings. It uses the proper archetype paths, filters for compositions created within the last year, and orders the results to get the latest readings.', + hint: 'Remember that AQL queries need to navigate the openEHR structure and use archetype paths. Also consider how to filter for the time range and order the results.', + }, + { + title: 'The Template Tactician', + description: + 'The Template Tactician challenges you to apply advanced template concepts in a real-world scenario!', + challenge: + 'A hospital needs to create a discharge summary template that includes vital signs, medication list, and follow-up instructions. Which approach best utilizes OpenEHR principles?', + options: [ + 'Create a new archetype that combines all the required elements', + 'Use a template to constrain and combine existing archetypes for each section', + 'Directly store the discharge summary data in the EHR without using archetypes or templates', + 'Create separate templates for each section and link them in the EHR', + ], + correctAnswer: 1, + explanation: + 'The best approach is to use a template to constrain and combine existing archetypes. This allows reuse of established archetypes for vital signs, medications, and instructions, while tailoring them to the specific needs of a discharge summary. It maintains consistency and interoperability while meeting the specific use case.', + hint: 'Think about how templates and archetypes work together, and the principle of reusing and constraining existing models.', + }, + { + title: 'The Archetype Architect', + description: + 'The Archetype Architect tests your understanding of archetype design principles and best practices!', + challenge: + "You need to design an archetype for recording a patient's smoking history. Which of the following approaches is most aligned with OpenEHR best practices?", + options: [ + 'Create a single, comprehensive archetype that covers all possible smoking-related data points', + 'Design multiple, highly specific archetypes for each aspect of smoking history', + "Use an existing generic 'lifestyle factor' archetype and constrain it for smoking", + 'Create a moderate-sized archetype focusing on key smoking history elements, with the ability to extend or specialize later', + ], + correctAnswer: 3, + explanation: + 'The best approach is to create a moderate-sized archetype focusing on key smoking history elements, with the ability to extend or specialize later. This balances the need for specific smoking-related data capture with the OpenEHR principles of reusability and maintainability. It allows for future refinement without overcomplicating the initial design.', + hint: 'Consider the balance between specificity and reusability in archetype design, as well as the potential for future extensions.', + }, + { + title: 'The Versioning Virtuoso', + description: + 'The Versioning Virtuoso challenges your understanding of change management in OpenEHR systems!', + challenge: + 'A critical error is discovered in a widely-used medication archetype. What is the appropriate way to handle this in an OpenEHR system?', + options: [ + 'Immediately replace the archetype with a corrected version in all systems', + 'Create a new version of the archetype, deprecate the old one, and update systems gradually', + 'Leave the archetype as-is and instruct users to be aware of the error', + 'Create a completely new archetype with a different ID to replace the erroneous one', + ], + correctAnswer: 1, + explanation: + "The appropriate approach is to create a new version of the archetype, deprecate the old one, and update systems gradually. This maintains backward compatibility for existing data while allowing systems to adopt the corrected version. It follows OpenEHR's principles of versioning and change management.", + hint: 'Think about how to balance the need for correction with the reality of existing data and systems using the current version.', + }, + { + title: 'The Semantic Sage', + description: + 'The Semantic Sage tests your ability to ensure semantic interoperability in OpenEHR implementations!', + challenge: + "You're integrating two OpenEHR systems that use different terminology bindings for medication names. What's the best approach to ensure semantic interoperability?", + options: [ + 'Force one system to adopt the terminology bindings of the other', + 'Create a mapping table between the two terminologies and translate during data exchange', + "Use OpenEHR's terminology binding features to map both to a common standard terminology", + 'Store medication names as free text to avoid terminology conflicts', + ], + correctAnswer: 2, + explanation: + "The best approach is to use OpenEHR's terminology binding features to map both systems to a common standard terminology. This preserves the local terminologies while ensuring semantic interoperability through a shared standard. It leverages OpenEHR's design for handling diverse terminologies.", + hint: 'Consider how OpenEHR is designed to handle terminology differences while maintaining semantic meaning across systems.', + }, + { + title: 'The Governance Guru', + description: + 'The Governance Guru challenges you to apply OpenEHR principles to data governance and privacy scenarios!', + challenge: + 'A multi-national research project wants to use OpenEHR for data collection across different countries with varying data protection laws. What approach best addresses this challenge?', + options: [ + 'Use a single, standardized OpenEHR template for all countries, ignoring local law variations', + 'Create separate, country-specific OpenEHR implementations for each participating nation', + "Use OpenEHR's archetype and template mechanism to create a core dataset, with country-specific extensions to meet local requirements", + 'Avoid using OpenEHR and opt for a custom, project-specific data model instead', + ], + correctAnswer: 2, + explanation: + "The best approach is to use OpenEHR's archetype and template mechanism to create a core dataset, with country-specific extensions to meet local requirements. This allows for a standardized base of data collection while providing the flexibility to adhere to varying local data protection laws. It leverages OpenEHR's design for balancing standardization with localization.", + hint: "Think about how OpenEHR's architecture can allow for both standardization and customization to meet diverse requirements.", + }, +]; diff --git a/src/openEHRQuest.tsx b/src/openEHRQuest.tsx deleted file mode 100644 index 4a6eafa..0000000 --- a/src/openEHRQuest.tsx +++ /dev/null @@ -1,443 +0,0 @@ -import React, { useState } from 'react'; -import { Terminal, Brain, Shield, Zap } from 'lucide-react'; -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardHeader, - CardContent, - CardFooter, -} from '@/components/ui/card'; -import { Progress } from '@/components/ui/progress'; -import { Badge } from '@/components/ui/badge'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui/tooltip'; - -interface Level { - title: string; - description: string; - challenge: string; - options: string[]; - correctAnswer: number; - explanation: string; - hint: string; -} - -interface GameState { - currentLevel: number; - score: number; - playerHealth: number; - badges: string[]; - hintUsed: boolean; -} - -const OpenEHRQuest: React.FC = () => { - const [gameState, setGameState] = useState({ - currentLevel: 0, - score: 0, - playerHealth: 100, - badges: [], - hintUsed: false, - }); - - const levels: Level[] = [ - { - title: 'The Template Tavern', - description: - 'Welcome to The Template Tavern! Your first quest is to decipher a mysterious web template. Choose the correct interpretation to proceed.', - challenge: - "You encounter a web template with the following structure:\n\n\n \n \n \n \n \n \n \n 120\n mm[Hg]\n \n \n \n \n \n \n \n", - options: [ - "It's a recipe for a health potion", - "It's a map to the hidden treasure of OpenEHR", - "It's a blood pressure measurement of 120 mm[Hg]", - "It's the secret code to unlock the next dungeon", - ], - correctAnswer: 2, - explanation: - "This template represents a blood pressure measurement. The 'observation' element with the archetype ID 'openEHR-EHR-OBSERVATION.blood_pressure.v1' indicates it's for blood pressure, and the value 120 mm[Hg] is clearly visible in the structure.", - hint: 'Look for the archetype ID and the value within the structure.', - }, - { - title: 'The Composition Conjurer', - description: - "You've made it to the Composition Conjurer's lair! Your task is to craft a composition that will impress the Conjurer.", - challenge: - "Create a composition for a patient's body temperature. The archetype ID is 'openEHR-EHR-OBSERVATION.body_temperature.v2'. The temperature is 37.5°C.", - options: [ - `{ - "composition": { - "content": [ - { - "observation": { - "archetype_id": "openEHR-EHR-OBSERVATION.body_temperature.v2", - "data": { - "events": [ - { - "data": { - "items": [ - { - "value": { - "magnitude": 37.5, - "units": "°C" - } - } - ] - } - } - ] - } - } - } - ] - } -}`, - `{ - "patient": { - "vitals": { - "temperature": 37.5, - "unit": "celsius" - } - } -}`, - `{ - "openEHR": { - "body_temperature": { - "value": 37.5, - "unit": "C" - } - } -}`, - `{ - "composition": { - "archetype": "openEHR-EHR-OBSERVATION.body_temperature.v2", - "temperature": "37.5°C" - } -}`, - ], - correctAnswer: 0, - explanation: - 'The correct composition follows the OpenEHR structure, including the proper archetype ID, and nests the temperature value within the expected data structure.', - hint: 'Consider the hierarchical structure of OpenEHR compositions and how data is typically represented.', - }, - { - title: 'The Integration Inquisitor', - description: - 'The Integration Inquisitor will test your knowledge of posting compositions to an OpenEHR system.', - challenge: - 'Which HTTP method and content type should you use when posting a composition to an OpenEHR system?', - options: [ - 'GET request with application/json content type', - 'POST request with application/xml content type', - 'PUT request with text/plain content type', - 'POST request with application/json content type', - ], - correctAnswer: 3, - explanation: - "When posting a new composition to an OpenEHR system, you typically use a POST request with application/json content type. This allows you to send structured data in a format that's widely supported and easy to work with.", - hint: 'Think about which HTTP method is typically used for creating new resources.', - }, - { - title: 'The AQL Alchemist', - description: - 'Enter the laboratory of the AQL Alchemist, where queries are brewed to extract the essence of clinical data!', - challenge: - "Which AQL query will retrieve all blood pressure measurements for a specific patient with EHR ID 'abc123'?", - options: [ - "SELECT o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude AS systolic, o/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude AS diastolic FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v1] WHERE e/ehr_id/value='abc123'", - "SELECT * FROM EHR WHERE patient_id = 'abc123' AND observation_type = 'blood_pressure'", - "GET blood_pressure FROM patient WHERE id = 'abc123'", - "FIND 'blood pressure' IN EHR 'abc123'", - ], - correctAnswer: 0, - explanation: - 'This AQL query correctly navigates the openEHR structure to retrieve systolic and diastolic blood pressure values from the specific archetype, filtering for the given EHR ID.', - hint: 'AQL uses a structure similar to SQL, but with paths that navigate the openEHR data model.', - }, - { - title: 'The Versioning Vault', - description: - 'Welcome to the Versioning Vault, where the history of health records is preserved through the ages!', - challenge: - 'When retrieving a specific version of a composition, which of the following is true?', - options: [ - 'You must always retrieve the latest version', - 'You can specify a version number or time to get a specific historical version', - 'Versioning is not supported in OpenEHR', - 'You need to retrieve all versions and manually find the one you want', - ], - correctAnswer: 1, - explanation: - 'OpenEHR supports versioning of compositions. You can retrieve a specific version by specifying either a version number or a timestamp, allowing access to the historical state of a composition at a particular point in time.', - hint: 'Think about how you might want to access historical data in a medical record system.', - }, - { - title: 'The Binding Bard', - description: - 'The Binding Bard challenges you to understand the intricacies of binding terminologies in OpenEHR!', - challenge: - 'Which method is used to bind external terminologies to archetype nodes?', - options: [ - 'Direct insertion of codes into archetypes', - "Using the 'term_bindings' section in archetypes", - 'Creating separate terminology archetypes', - 'Modifying the reference model to include terminologies', - ], - correctAnswer: 1, - explanation: - "OpenEHR uses the 'term_bindings' section in archetypes to bind external terminologies. This allows for flexible integration of various coding systems while maintaining the archetype structure.", - hint: 'Think about how OpenEHR maintains separation between its structure and external vocabularies.', - }, - { - title: 'The Operational Template Oracle', - description: - 'The Operational Template Oracle tests your knowledge of OPTs in the OpenEHR ecosystem!', - challenge: - 'What is the primary purpose of an Operational Template (OPT) in OpenEHR?', - options: [ - 'To replace archetypes in modern OpenEHR implementations', - 'To provide a fully specialized and flattened form of a template for direct use in systems', - 'To create new archetypes based on existing ones', - 'To define the operational procedures in a healthcare setting', - ], - correctAnswer: 1, - explanation: - "An Operational Template (OPT) in OpenEHR is a fully specialized and flattened form of a template. It's designed for direct use in systems, containing all the necessary information from archetypes and templates in a readily usable format.", - hint: 'Consider what form of templates would be most immediately usable by EHR systems.', - }, - { - title: 'The Composition Composer', - description: - 'The Composition Composer challenges you to orchestrate the elements of an OpenEHR composition!', - challenge: - 'Which of the following is NOT typically found at the root level of an OpenEHR composition?', - options: ['context', 'category', 'content', 'observation'], - correctAnswer: 3, - explanation: - "In an OpenEHR composition, 'observation' is not typically found at the root level. The root level usually includes 'context', 'category', and 'content'. Observations and other clinical entry types are usually nested within the 'content' section.", - hint: 'Think about the high-level structure of a composition and what elements define its overall properties.', - }, - { - title: 'The Constraint Connoisseur', - description: - 'The Constraint Connoisseur tests your ability to understand and apply constraints in OpenEHR archetypes!', - challenge: - 'Which of the following is a valid way to constrain a quantity data type in an archetype?', - options: [ - 'Setting a specific value that must be used', - 'Defining a range of allowed values', - 'Specifying a list of allowed units', - 'All of the above', - ], - correctAnswer: 3, - explanation: - 'In OpenEHR archetypes, quantity data types can be constrained in multiple ways. You can set a specific value, define a range of allowed values, or specify a list of allowed units. Often, a combination of these constraints is used to precisely define the acceptable values for a quantity.', - hint: 'Consider the various ways you might want to limit or specify how a quantity can be recorded in a clinical context.', - }, - { - title: 'The Persistence Pilgrim', - description: - 'The Persistence Pilgrim challenges your understanding of data storage in OpenEHR systems!', - challenge: - 'Which statement best describes how data is typically stored in an OpenEHR system?', - options: [ - 'Data is stored in relational tables matching the structure of archetypes', - 'Each composition is stored as a single document in a NoSQL database', - 'Data is stored in a node + path + value format, preserving the hierarchical structure', - 'Archetypes themselves store the data values directly', - ], - correctAnswer: 2, - explanation: - 'In many OpenEHR implementations, data is typically stored in a node + path + value format. This approach preserves the hierarchical structure defined by archetypes and templates, while allowing for efficient storage and querying of the data.', - hint: 'Think about a storage method that would allow for flexible querying while maintaining the structure defined by archetypes.', - }, - ]; - - const handleAnswer = (selectedIndex: number) => { - const currentLevel = levels[gameState.currentLevel]; - if (selectedIndex === currentLevel.correctAnswer) { - setGameState((prevState) => ({ - ...prevState, - score: prevState.score + (prevState.hintUsed ? 50 : 100), - currentLevel: prevState.currentLevel + 1, - hintUsed: false, - badges: [ - ...prevState.badges, - `Level ${prevState.currentLevel + 1} Master`, - ], - })); - } else { - setGameState((prevState) => ({ - ...prevState, - playerHealth: prevState.playerHealth - 20, - hintUsed: false, - })); - } - }; - - const useHint = () => { - setGameState((prevState) => ({ ...prevState, hintUsed: true })); - }; - - const resetGame = () => { - setGameState({ - currentLevel: 0, - score: 0, - playerHealth: 100, - badges: [], - hintUsed: false, - }); - }; - - if (gameState.playerHealth <= 0) { - return ( -
- - -

Game Over

-
- -

- Your OpenEHR journey has come to an end. -

-

Final Score: {gameState.score}

-
- - - -
-
- ); - } - - if (gameState.currentLevel >= levels.length) { - return ( -
- - -

- 🎉 Congratulations! 🎉 -

-
- -

- You've become an OpenEHR Integration Master! -

-

Final Score: {gameState.score}

-
- {gameState.badges.map((badge, index) => ( - - {badge} - - ))} -
-
- - - -
-
- ); - } - - const currentLevel = levels[gameState.currentLevel]; - - return ( -
- - -

- {currentLevel.title} -

-

- Level {gameState.currentLevel + 1} of {levels.length} -

-
- - - - Mission Briefing - {currentLevel.description} - -
-

Challenge:

-
-              {currentLevel.challenge}
-            
-
-
- {currentLevel.options.map((option, index) => ( - - ))} -
-
-
- - - - - - -

- {gameState.hintUsed - ? 'Hint already used' - : 'Click to reveal a hint (reduces points for this level)'} -

-
-
-
-
- {gameState.badges.slice(-3).map((badge, index) => ( - - {badge} - - ))} -
-
- {gameState.hintUsed && ( - - Hint - {currentLevel.hint} - - )} -
-
- -
-
- - Score: {gameState.score} -
-
- - Health: {gameState.playerHealth}% -
-
- -
-
-
- ); -}; - -export default OpenEHRQuest;