@@ -324,4 +324,354 @@ public static Boolean deriveClinicalCriteriaStatus(YesNoUnknown clinicalConfirma
324324 }
325325 return clinicalConfirmation == YesNoUnknown .YES ;
326326 }
327+
328+ // ==================== IPI-Specific Mappers ====================
329+
330+ /**
331+ * Maps SORMAS SampleMaterial enum to EpiPulse IPI specimen type codes.
332+ * <p>
333+ * EpiPulse Reference Values for IPI:
334+ * - BLOOD = Blood
335+ * - CSF = Cerebrospinal fluid
336+ * - OTH = Other
337+ * - PLEURAL = Pleural fluid
338+ * - SER = Serum
339+ * - SYNOVIAL = Synovial fluid (joint fluid)
340+ * - THROAT = Throat swab
341+ * - NPSWAB = Nasopharyngeal swab
342+ *
343+ * @param sampleMaterial
344+ * SORMAS sample material enum
345+ * @return EpiPulse IPI specimen type code, or null if not mappable
346+ */
347+ public static String mapSampleMaterialToIpiSpecimenCode (SampleMaterial sampleMaterial ) {
348+ if (sampleMaterial == null ) {
349+ return null ;
350+ }
351+
352+ switch (sampleMaterial ) {
353+ case BLOOD :
354+ return "BLOOD" ;
355+ case SERA :
356+ return "SER" ; // Serum
357+ case CEREBROSPINAL_FLUID :
358+ return "CSF" ;
359+ case PLEURAL_FLUID :
360+ return "PLEURAL" ;
361+ case SYNOVIAL_FLUID :
362+ return "SYNOVIAL" ;
363+ case THROAT_SWAB :
364+ return "THROAT" ;
365+ case NP_SWAB :
366+ return "NPSWAB" ;
367+ case OTHER :
368+ return "OTH" ;
369+ default :
370+ return null ;
371+ }
372+ }
373+
374+ /**
375+ * Maps SORMAS Symptoms to EpiPulse IPI clinical presentation codes.
376+ * <p>
377+ * EpiPulse Reference Values for IPI:
378+ * - MENING = Meningitis
379+ * - SEPT = Septicaemia
380+ * - PNEUM = Pneumonia
381+ * - OME = Otitis media
382+ * - PERITON = Peritonitis
383+ * - ARTH = Arthritis
384+ * - OTH = Other
385+ * - ASYMP = Asymptomatic
386+ * - NONE = No clinical presentation recorded
387+ *
388+ * @param meningitis
389+ * Meningitis symptom state (IPI-specific)
390+ * @param septicaemia
391+ * Septicaemia symptom state (IPI-specific)
392+ * @param pneumonia
393+ * Pneumonia symptom state
394+ * @param otitisMedia
395+ * Otitis media symptom state
396+ * @param peritonitis
397+ * Peritonitis symptom state
398+ * @param arthritis
399+ * Arthritis symptom state
400+ * @param otherClinical
401+ * Other clinical presentation symptom state
402+ * @param asymptomatic
403+ * Asymptomatic symptom state
404+ * @return List of EpiPulse clinical presentation codes (empty list returns "NONE" in CSV)
405+ */
406+ public static List <String > mapSymptomsToClinicalPresentation (
407+ SymptomState meningitis ,
408+ SymptomState septicaemia ,
409+ SymptomState pneumonia ,
410+ SymptomState otitisMedia ,
411+ SymptomState peritonitis ,
412+ SymptomState arthritis ,
413+ SymptomState otherClinical ,
414+ SymptomState asymptomatic ) {
415+
416+ List <String > presentations = new ArrayList <>();
417+
418+ // Priority: IPI-defining symptoms first
419+ if (meningitis == SymptomState .YES ) {
420+ presentations .add ("MENING" );
421+ }
422+ if (septicaemia == SymptomState .YES ) {
423+ presentations .add ("SEPT" );
424+ }
425+ if (pneumonia == SymptomState .YES ) {
426+ presentations .add ("PNEUM" );
427+ }
428+ if (otitisMedia == SymptomState .YES ) {
429+ presentations .add ("OME" );
430+ }
431+ if (peritonitis == SymptomState .YES ) {
432+ presentations .add ("PERITON" );
433+ }
434+ if (arthritis == SymptomState .YES ) {
435+ presentations .add ("ARTH" );
436+ }
437+ if (otherClinical == SymptomState .YES ) {
438+ presentations .add ("OTH" );
439+ }
440+ if (asymptomatic == SymptomState .YES ) {
441+ presentations .add ("ASYMP" );
442+ }
443+
444+ // If no presentations found, empty list will result in "NONE" in CSV
445+ return presentations ;
446+ }
447+
448+ /**
449+ * Maps SORMAS drug susceptibility test result to EpiPulse antibiotic resistance codes.
450+ * <p>
451+ * EpiPulse Reference Values:
452+ * - RESIST = Resistant
453+ * - SENS = Sensitive
454+ * - INTER = Intermediate resistance
455+ * - NOTEST = Not tested
456+ *
457+ * @param testResult
458+ * SORMAS PathogenTestResultType from drug susceptibility test
459+ * @return EpiPulse antibiotic resistance code
460+ */
461+ public static String mapDrugSusceptibilityToEpipulseCode (PathogenTestResultType testResult ) {
462+ if (testResult == null ) {
463+ return null ;
464+ }
465+
466+ switch (testResult ) {
467+ case POSITIVE :
468+ return "RESIST" ; // Positive drug susceptibility = Resistant
469+ case NEGATIVE :
470+ return "SENS" ; // Negative drug susceptibility = Sensitive
471+ case INDETERMINATE :
472+ return "INTER" ; // Intermediate resistance
473+ case PENDING :
474+ case NOT_DONE :
475+ return "NOTEST" ;
476+ default :
477+ return null ;
478+ }
479+ }
480+
481+ /**
482+ * Validates and normalizes pneumococcal serotype string for EpiPulse export.
483+ * <p>
484+ * Pneumococcal serotypes include 90+ types: 1, 2, 3, 4, 5, 6A, 6B, 7F, 8, 9N, 9V, 10A, etc.
485+ * Accepts formats like "6A", "19F", "23F", "SEROTYPE 6A", "TYPE 19F", etc.
486+ *
487+ * @param serotypeText
488+ * SORMAS serotype string (from PathogenTest typingId or serotype field)
489+ * @return Normalized EpiPulse serotype code (e.g., "6A", "19F"), or null if invalid
490+ */
491+ public static String normalizeSerotypeForEpipulse (String serotypeText ) {
492+ if (serotypeText == null || serotypeText .trim ().isEmpty ()) {
493+ return null ;
494+ }
495+
496+ String normalized = serotypeText .trim ().toUpperCase ();
497+
498+ // Remove common prefixes: "SEROTYPE ", "TYPE ", "S.", "PNEUMOCOCCAL ", etc.
499+ normalized = normalized .replaceAll ("^(SEROTYPE|TYPE|S\\ .|PNEUMOCOCCAL|STREPTOCOCCUS PNEUMONIAE)\\ s*" , "" );
500+
501+ // Validate format: digit(s) optionally followed by letter(s)
502+ // Examples: "1", "6A", "6B", "19F", "23F", "15A", "33F"
503+ if (normalized .matches ("^\\ d{1,2}[A-Z]{0,2}$" )) {
504+ return normalized ;
505+ }
506+
507+ return null ; // Invalid format
508+ }
509+
510+ /**
511+ * Maps SORMAS Symptoms to EpiPulse ClinicalCriteria codes for PNEU.
512+ * <p>
513+ * EpiPulse Reference Values for PNEU ClinicalCriteria:
514+ * - BACTERPNEUMO = Bacteraemic pneumonia
515+ * - MENI = Meningitis/Meningeal/Meningoencephalitic
516+ * - MENISEPTI = Meningitis and septicaemia
517+ * - OTH = Other
518+ * - SEPTI = Septicaemia
519+ *
520+ * @param meningitis Meningitis symptom state
521+ * @param septicaemia Septicaemia symptom state
522+ * @param pneumonia Pneumonia symptom state (clinical or radiologic)
523+ * @return EpiPulse clinical criteria code, or null if no criteria met
524+ */
525+ public static String mapSymptomsToClinicalCriteria (
526+ SymptomState meningitis ,
527+ SymptomState septicaemia ,
528+ SymptomState pneumonia ) {
529+
530+ boolean hasMeningitis = meningitis == SymptomState .YES ;
531+ boolean hasSepticaemia = septicaemia == SymptomState .YES ;
532+ boolean hasPneumonia = pneumonia == SymptomState .YES ;
533+
534+ // Priority order based on severity/specificity
535+ if (hasMeningitis && hasSepticaemia ) {
536+ return "MENISEPTI" ; // Both present
537+ } else if (hasMeningitis ) {
538+ return "MENI" ; // Meningitis only
539+ } else if (hasSepticaemia ) {
540+ return "SEPTI" ; // Septicaemia only
541+ } else if (hasPneumonia ) {
542+ return "BACTERPNEUMO" ; // Bacteraemic pneumonia
543+ }
544+
545+ return null ; // No specific clinical criteria met
546+ }
547+
548+ /**
549+ * Maps SORMAS Vaccine enum to EpiPulse vaccine codes for PNEU.
550+ * <p>
551+ * EpiPulse Reference Values for PNEU Vaccine:
552+ * - PCV7 = Pneumococcal conjugate vaccine 7
553+ * - PCV10 = Pneumococcal conjugate vaccine 10
554+ * - PCV13 = Pneumococcal conjugate vaccine 13
555+ * - PCV15 = Pneumococcal conjugate vaccine 15
556+ * - PCV20 = Pneumococcal conjugate vaccine 20
557+ * - PCV3 = Pneumococcal conjugate vaccine - third dose
558+ * - PPV23 = Pneumococcal polysaccharide vaccine
559+ *
560+ * @param vaccineName SORMAS vaccine name/type
561+ * @return EpiPulse vaccine code, or null if not pneumococcal vaccine
562+ */
563+ public static String mapVaccineToEpipulseCode (String vaccineName ) {
564+ if (vaccineName == null || vaccineName .trim ().isEmpty ()) {
565+ return null ;
566+ }
567+
568+ String normalized = vaccineName .trim ().toUpperCase ();
569+
570+ // Map PCV vaccines
571+ if (normalized .contains ("PCV" ) || normalized .contains ("CONJUGATE" )) {
572+ if (normalized .contains ("20" )) {
573+ return "PCV20" ;
574+ } else if (normalized .contains ("15" )) {
575+ return "PCV15" ;
576+ } else if (normalized .contains ("13" )) {
577+ return "PCV13" ;
578+ } else if (normalized .contains ("10" )) {
579+ return "PCV10" ;
580+ } else if (normalized .contains ("7" )) {
581+ return "PCV7" ;
582+ } else if (normalized .contains ("3" )) {
583+ return "PCV3" ;
584+ }
585+ // Default PCV if no specific number
586+ return "PCV13" ; // Most common
587+ }
588+
589+ // Map PPV vaccine
590+ if (normalized .contains ("PPV" ) || normalized .contains ("POLYSACCHARIDE" ) || normalized .contains ("23" )) {
591+ return "PPV23" ;
592+ }
593+
594+ // Check for general pneumococcal terms
595+ if (normalized .contains ("PNEUMO" )) {
596+ return "PCV13" ; // Default to most common PCV
597+ }
598+
599+ return null ; // Not a pneumococcal vaccine
600+ }
601+
602+ /**
603+ * Maps DrugSusceptibilityType enum to EpiPulse SIR codes.
604+ * <p>
605+ * EpiPulse Reference Values for SIR (Susceptible/Intermediate/Resistant):
606+ * - S = Susceptible
607+ * - I = Intermediate
608+ * - R = Resistant
609+ *
610+ * @param susceptibility SORMAS drug susceptibility enum
611+ * @return EpiPulse SIR code (S/I/R), or null if not tested
612+ */
613+ public static String mapDrugSusceptibilityToSIR (String susceptibility ) {
614+ if (susceptibility == null || susceptibility .trim ().isEmpty ()) {
615+ return null ;
616+ }
617+
618+ String normalized = susceptibility .trim ().toUpperCase ();
619+
620+ if (normalized .equals ("SUSCEPTIBLE" ) || normalized .equals ("S" )) {
621+ return "S" ;
622+ } else if (normalized .equals ("INTERMEDIATE" ) || normalized .equals ("I" )) {
623+ return "I" ;
624+ } else if (normalized .equals ("RESISTANT" ) || normalized .equals ("R" )) {
625+ return "R" ;
626+ }
627+
628+ return null ; // Unknown susceptibility
629+ }
630+
631+ /**
632+ * Maps SORMAS PathogenTestType to EpiPulse PathogenDetectionMethod codes for PNEU.
633+ * <p>
634+ * EpiPulse Reference Values for PNEU PathogenDetectionMethod:
635+ * - COAGG = Coagglutination
636+ * - GDIFF = Gel diffusion
637+ * - MPCR = Multiplex PCR
638+ * - OTH = Other
639+ * - PTEST = Pneumotest
640+ * - QUE = Quellung
641+ * - SLAGG = Slide agglutination
642+ *
643+ * @param testType SORMAS pathogen test type
644+ * @return EpiPulse detection method code, or null if not mappable
645+ */
646+ public static String mapPathogenTestTypeToDetectionMethod (String testType ) {
647+ if (testType == null || testType .trim ().isEmpty ()) {
648+ return null ;
649+ }
650+
651+ String normalized = testType .trim ().toUpperCase ();
652+
653+ // Map specific test types
654+ if (normalized .contains ("PCR" ) || normalized .contains ("RT-PCR" ) || normalized .contains ("RTPCR" )) {
655+ if (normalized .contains ("MULTIPLEX" )) {
656+ return "MPCR" ; // Multiplex PCR
657+ }
658+ return "MPCR" ; // Default PCR to multiplex
659+ } else if (normalized .contains ("QUELLUNG" )) {
660+ return "QUE" ;
661+ } else if (normalized .contains ("COAGG" ) || normalized .contains ("CO-AGGLUTINATION" )) {
662+ return "COAGG" ;
663+ } else if (normalized .contains ("SLIDE" ) && normalized .contains ("AGGLUT" )) {
664+ return "SLAGG" ;
665+ } else if (normalized .contains ("GEL" ) && normalized .contains ("DIFF" )) {
666+ return "GDIFF" ;
667+ } else if (normalized .contains ("PNEUMOTEST" )) {
668+ return "PTEST" ;
669+ } else if (normalized .contains ("CULTURE" )) {
670+ return "QUE" ; // Culture often followed by Quellung
671+ } else if (normalized .contains ("SEROGROUPING" ) || normalized .contains ("SEROTYPING" )) {
672+ return "QUE" ; // Serotyping typically uses Quellung
673+ }
674+
675+ return "OTH" ; // Other methods
676+ }
327677}
0 commit comments