Jelajahi Sumber

Merge branch 'dev-vj-fdb' into dev-vj

Vijayakrishnan 3 tahun lalu
induk
melakukan
2f0baefc87

+ 6 - 0
.env.example

@@ -13,6 +13,12 @@ DB_DATABASE=stag2
 DB_USERNAME=postgres
 DB_PASSWORD=pass
 
+DB_HOST_FDB=127.0.0.1
+DB_PORT_FDB=5432
+DB_DATABASE_FDB=fdb
+DB_USERNAME_FDB=postgres
+DB_PASSWORD_FDB=password
+
 BROADCAST_DRIVER=log
 CACHE_DRIVER=file
 QUEUE_CONNECTION=sync

+ 620 - 0
app/Http/Controllers/FDBPGController.php

@@ -0,0 +1,620 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+class FDBPGController extends Controller
+{
+
+    public function rx(Request $request)
+    {
+        return view('app.fdb-pg.fdb-rx');
+    }
+
+    public function medSuggest(Request $request)
+    {
+        $term = $request->input('term') ? trim($request->input('term')) : '';
+        if (empty($term)) return '';
+        $matches = DB::connection('pgsql_fdb')->select(
+            "SELECT med_name_id, med_name FROM rminmid1_med_name WHERE med_status_cd = '0' AND med_name ILIKE :term ORDER BY med_name",
+            ['term' => '%' . $term . '%']
+        );
+        return view('app.fdb-pg.fdb-med-suggest', compact('matches'));
+    }
+
+    public function routedMeds(Request $request)
+    {
+        $medNameID = $request->input('med-name-id') ? trim($request->input('med-name-id')) : '';
+        if (empty($medNameID)) return '';
+        $matches = DB::connection('pgsql_fdb')->select(
+            "SELECT routed_med_id, med_routed_med_id_desc FROM rmirmid1_routed_med WHERE med_status_cd = '0' AND med_name_id = :medNameID ORDER BY med_routed_med_id_desc",
+            ['medNameID' => $medNameID]
+        );
+        return json_encode($matches);
+    }
+
+    public function routedDosages(Request $request)
+    {
+        $routedMedID = $request->input('routed-med-id') ? trim($request->input('routed-med-id')) : '';
+        if (empty($routedMedID)) return '';
+        $matches = DB::connection('pgsql_fdb')->select(
+            "SELECT routed_dosage_form_med_id, med_routed_df_med_id_desc FROM rmidfid1_routed_dose_form_med WHERE med_status_cd = '0' AND routed_med_id = :routedMedID ORDER BY med_routed_df_med_id_desc",
+            ['routedMedID' => $routedMedID]
+        );
+        return json_encode($matches);
+    }
+
+    public function meds(Request $request)
+    {
+        $dosageFormMedId = $request->input('dosage-form-med-id') ? trim($request->input('dosage-form-med-id')) : '';
+        if (empty($dosageFormMedId)) return '';
+        $matches = DB::connection('pgsql_fdb')->select(
+            "SELECT medid, med_medid_desc, gcn_seqno FROM rmiid1_med WHERE med_status_cd = '0' AND routed_dosage_form_med_id = :dosageFormMedId ORDER BY med_medid_desc",
+            ['dosageFormMedId' => $dosageFormMedId]
+        );
+        return json_encode($matches);
+    }
+
+    public function sideEffects(Request $request)
+    {
+        $gcnSeqNo = $request->input('gcn-seq-no') ? trim($request->input('gcn-seq-no')) : '';
+        if (empty($gcnSeqNo)) return '';
+        $sides = DB::connection('pgsql_fdb')->select("
+SELECT r1.side, sm.side_freq, sm.side_sev, sm.dxid, dx.dxid_desc56 
+FROM rsidegc0_gcnseqno_link r1 
+    JOIN rsidema3_mstr sm ON r1.side = sm.side
+    JOIN rfmldx0_dxid dx ON sm.dxid = dx.dxid
+WHERE r1.gcn_seqno = :gcnSeqNo
+ORDER BY sm.side_sev DESC, sm.side_freq ASC
+            ",
+            ['gcnSeqNo' => $gcnSeqNo]
+        );
+        return view('app.fdb-pg.fdb-side-effects', compact('sides'));
+    }
+
+    public function geriatricPrecautions(Request $request)
+    {
+        $gcnSeqNo = $request->input('gcn-seq-no') ? trim($request->input('gcn-seq-no')) : '';
+        if (empty($gcnSeqNo)) return '';
+        $precautions = DB::connection('pgsql_fdb')->select("
+SELECT r1.geri_code, gm.geri_sl, gm.geri_desc, gm.geri_narrative
+FROM rgerigc0_geri_gcnseqno_link r1 
+    JOIN rgerima1_geri_mstr gm ON r1.geri_code = gm.geri_code
+WHERE r1.gcn_seqno = :gcnSeqNo
+ORDER BY gm.geri_desc
+            ",
+            ['gcnSeqNo' => $gcnSeqNo]
+        );
+        return view('app.fdb-pg.fdb-geriatric-precautions', compact('precautions'));
+    }
+
+    public function indications(Request $request)
+    {
+        $gcnSeqNo = $request->input('gcn-seq-no') ? trim($request->input('gcn-seq-no')) : '';
+        if (empty($gcnSeqNo)) return '';
+        $indications = DB::connection('pgsql_fdb')->select("
+SELECT r1.indcts, r2.indcts_sn, r2.indcts_lbl, r2.dxid, r2.proxy_ind, r3.dxid_desc56
+FROM rindmgc0_indcts_gcnseqno_link r1 
+    JOIN rindmma2_indcts_mstr r2 ON r1.indcts = r2.indcts
+    JOIN rfmldx0_dxid r3 ON r2.dxid = r3.dxid
+WHERE r1.gcn_seqno = :gcnSeqNo
+ORDER BY r3.dxid_desc56
+            ",
+            ['gcnSeqNo' => $gcnSeqNo]
+        );
+        return view('app.fdb-pg.fdb-indications', compact('indications'));
+    }
+
+    public function contraindications(Request $request)
+    {
+        $routedMedID = $request->input('routed-med-id') ? trim($request->input('routed-med-id')) : '';
+        if (empty($routedMedID)) return '';
+        $contraindications = DB::connection('pgsql_fdb')->select("
+SELECT r1.ddxcn, r2.dxid, r2.ddxcn_sl, r3.dxid_desc56
+FROM rddcmrm0_routed_med_link r1 
+    JOIN rddcmma1_contra_mstr r2 ON r1.ddxcn = r2.ddxcn
+    JOIN rfmldx0_dxid r3 ON r2.dxid = r3.dxid
+WHERE r1.routed_med_id = :routedMedID
+ORDER BY r2.ddxcn_sl
+            ",
+            ['routedMedID' => $routedMedID]
+        );
+        return view('app.fdb-pg.fdb-contraindications', compact('contraindications'));
+    }
+
+    public function dxSuggest(Request $request)
+    {
+        $term = $request->input('term') ? trim($request->input('term')) : '';
+        if (empty($term)) return '';
+        $matches = DB::connection('pgsql_fdb')->select("
+SELECT distinct(r1.dxid), r1.dxid_desc56 
+FROM rfmldx0_dxid r1
+JOIN rfmlsyn0_dxid_syn r2 ON r1.dxid = r2.dxid
+WHERE (r1.dxid_desc56 ILIKE :term OR r1.dxid_desc100 ILIKE :term OR r2.dxid_syn_desc56 ILIKE :term OR r2.dxid_syn_desc100 ILIKE :term)
+ORDER BY r1.dxid_desc56
+",
+            ['term' => '%' . $term . '%']
+        );
+        return view('app.fdb-pg.fdb-dx-suggest', compact('matches'));
+    }
+
+    public function allergySuggest(Request $request)
+    {
+        $term = $request->input('term') ? trim($request->input('term')) : '';
+        if (empty($term)) return '';
+        $matches = DB::connection('pgsql_fdb')->select("
+SELECT r1.dam_concept_id, r1.dam_concept_id_typ, r1.dam_concept_id_desc
+FROM rdamca0_concept r1
+WHERE (r1.dam_concept_id_desc ILIKE :term)
+ORDER BY r1.dam_concept_id_desc
+",
+            ['term' => '%' . $term . '%']
+        );
+        return view('app.fdb-pg.fdb-allergy-suggest', compact('matches'));
+    }
+
+    public function drugAllergies(Request $request) {
+
+        // override
+        if($request->input('test')) {
+            $x = new \stdClass();
+            $x->allergen = 'Pollen';
+            $x->dam_concept_id_typ = 6;
+            $allergies = [$x];
+            $y = new \stdClass();
+            $y->rx = 'Brufen';
+            $rx = [$y];
+        }
+        else {
+            $input = json_decode($request->input('data'));
+            $allergies = $input->allergies;
+            $rx = $input->rx;
+        }
+
+        $output = [];
+
+        /*
+        for each allergy
+            if dam_concept_id_typ = 1 // allergen-group-id
+                // https://docs.fdbhealth.com/display/MKDOCUS/Screening+an+NDC+for+a+DAM_ALRGN_GRP+Allergen+-+Illustration+of+Scenario+C
+                ...
+            elseif dam_concept_id_typ = 2 // medication-name-id
+                // https://docs.fdbhealth.com/display/MKDOCUS/Screening+an+NDC+for+a+MED_NAME_ID+Allergen+-+Illustration+of+Scenario+B
+                ...
+            elseif dam_concept_id_typ = 6 // base-ingredient-id
+                // https://docs.fdbhealth.com/display/MKDOCUS/Screening+an+NDC+for+an+Ingredient+Allergen+-+Illustration+of+Scenario+A
+                ...
+            endif
+        endfor
+        */
+
+        foreach ($allergies as $allergy) {
+            foreach ($rx as $rxItem) {
+                if($allergy->dam_concept_id_typ == 6) { // ingredient
+                    if ($this->drugAllergyIngredientAllergenVsSingleRx($allergy, $rxItem)) {
+                        $output[] = "<b>{$rxItem->rx}</b> can cause allergic reactions since the patient is allergic to <b>{$allergy->allergen}</b>.";
+                    }
+                }
+                else if($allergy->dam_concept_id_typ == 2) { // medication
+                    if ($this->drugAllergyMedicationAllergenVsSingleRx($allergy, $rxItem)) {
+                        $output[] = "<b>{$rxItem->rx}</b> can cause allergic reactions since the patient is allergic to <b>{$allergy->allergen}</b>.";
+                    }
+                }
+                else if($allergy->dam_concept_id_typ == 1) { // allergen group
+                    if ($this->drugAllergyGroupAllergenVsSingleRx($allergy, $rxItem)) {
+                        $output[] = "<b>{$rxItem->rx}</b> can cause allergic reactions since the patient is allergic to <b>{$allergy->allergen}</b>.";
+                    }
+                }
+            }
+        }
+
+        return implode("<br>", $output);
+
+    }
+
+    private function drugAllergyIngredientAllergenVsSingleRx($_allergen, $_rx) {
+
+        $matches = DB::connection('pgsql_fdb')->select("
+(
+    -- ingredients from medication
+    SELECT R1.related_hic_seqn as hic_seqn, R2.hic_desc
+    FROM RHICHCR0_HIC_HIC_LINK R1
+             JOIN RHICD5_HIC_DESC R2 ON R1.related_hic_seqn = R2.hic_seqn
+    WHERE R1.hic_seqn IN (
+        SELECT S2.dam_alrgn_hic_seqn
+        FROM RMEDMHL0_MED_HICLSEQNO_LINK S1
+                 JOIN RDAMHHA0_HIC_HICL_ALG_LINK S2 ON S1.hicl_seqno = S2.hicl_seqno
+        WHERE S1.med_concept_id = :medid
+          AND S1.med_concept_id_typ = 3
+    )
+)
+INTERSECT
+(
+    -- all ingredients directly and related from allergens
+    (
+        SELECT R1.related_hic_seqn as hic_seqn, R2.hic_desc
+        FROM RHICHCR0_HIC_HIC_LINK R1
+                 JOIN RHICD5_HIC_DESC R2 ON R1.related_hic_seqn = R2.hic_seqn
+        WHERE R1.hic_seqn = :allergenConceptID
+    )
+    UNION
+    -- all ingredients via related dam allergen groups
+    (
+        SELECT r3.hic_seqn, r4.hic_desc
+        FROM RDAMGHC0_HIC_ALRGN_GRP_LINK R1
+                 JOIN rdamagd1_alrgn_grp_desc R2 on R1.dam_alrgn_grp = R2.dam_alrgn_grp
+                 JOIN RDAMGHC0_HIC_ALRGN_GRP_LINK R3 on R1.dam_alrgn_grp = R3.dam_alrgn_grp
+                 JOIN RHICD5_HIC_DESC R4 on r3.hic_seqn = r4.hic_seqn
+        WHERE R1.hic_seqn = :allergenConceptID
+          AND R2.dam_alrgn_grp_status_cd = 0
+        ORDER BY r3.hic_seqn
+    )
+)
+",
+            ['medid' => $_rx->medid, 'allergenConceptID' => $_allergen->dam_concept_id]
+        );
+
+        return !!count($matches);
+    }
+
+    private function drugAllergyMedicationAllergenVsSingleRx($_allergen, $_rx) {
+
+        $matches = DB::connection('pgsql_fdb')->select("
+(
+    -- ingredients from medication
+    SELECT R1.related_hic_seqn as hic_seqn
+    FROM RHICHCR0_HIC_HIC_LINK R1
+             JOIN RHICD5_HIC_DESC R2 ON R1.related_hic_seqn = R2.hic_seqn
+    WHERE R1.hic_seqn IN (
+        SELECT S2.dam_alrgn_hic_seqn
+        FROM RMEDMHL0_MED_HICLSEQNO_LINK S1
+                 JOIN RDAMHHA0_HIC_HICL_ALG_LINK S2 ON S1.hicl_seqno = S2.hicl_seqno
+        WHERE S1.med_concept_id = :medid
+          AND S1.med_concept_id_typ = 3
+    )
+)
+INTERSECT
+(
+    -- all ingredients directly and related from allergens
+    SELECT R1.dam_alrgn_hic_seqn as hic_seqn
+    FROM RDAMHHA0_HIC_HICL_ALG_LINK R1
+    WHERE R1.hicl_seqno IN (
+        SELECT R1.hicl_seqno
+        FROM RMEDMHL0_MED_HICLSEQNO_LINK R1
+        WHERE R1.med_concept_id_typ = 1
+          AND R1.med_concept_id = :allergenConceptID
+          AND MED_CONCEPT_HICL_SRC_CD = 0
+    )
+)
+",
+            ['medid' => $_rx->medid, 'allergenConceptID' => $_allergen->dam_concept_id]
+        );
+
+        return !!count($matches);
+    }
+
+    private function drugAllergyGroupAllergenVsSingleRx($_allergen, $_rx) {
+
+        $matches = DB::connection('pgsql_fdb')->select("
+(
+    -- ingredients from medication
+    SELECT R1.related_hic_seqn as hic_seqn
+    FROM RHICHCR0_HIC_HIC_LINK R1
+             JOIN RHICD5_HIC_DESC R2 ON R1.related_hic_seqn = R2.hic_seqn
+    WHERE R1.hic_seqn IN (
+        SELECT S2.dam_alrgn_hic_seqn
+        FROM RMEDMHL0_MED_HICLSEQNO_LINK S1
+                 JOIN RDAMHHA0_HIC_HICL_ALG_LINK S2 ON S1.hicl_seqno = S2.hicl_seqno
+        WHERE S1.med_concept_id = :medid
+          AND S1.med_concept_id_typ = 3
+    )
+)
+INTERSECT
+(
+    -- ingredients from medication allergen
+    (
+        SELECT R1.hic_seqn as hic_seqn
+        FROM RDAMGHC0_HIC_ALRGN_GRP_LINK R1
+        WHERE R1.DAM_ALRGN_GRP = :allergenConceptID
+    )
+    UNION
+    (
+        SELECT distinct s1.hic_seqn
+        FROM RDAMXHC0_HIC_ALRGN_XSENSE_LINK S1
+        WHERE S1.dam_alrgn_xsense IN (
+            SELECT R1.dam_alrgn_xsense
+            FROM RDAMGX0_ALRGN_GRP_XSENSE_LINK R1
+                     JOIN RDAMCSD1_XSENSIT_ALLERGY_DESC R2 on R1.dam_alrgn_xsense = R2.dam_alrgn_xsense
+            WHERE R1.dam_alrgn_grp = :allergenConceptID
+              AND R2.dam_alrgn_xsense_status_cd = 0
+        )
+    )
+)
+",
+            ['medid' => $_rx->medid, 'allergenConceptID' => $_allergen->dam_concept_id]
+        );
+
+        return !!count($matches);
+    }
+
+    public function drugDrugInteraction(Request $request) {
+
+        if($request->input('test')) {
+            // override
+            $rx = json_decode(json_encode([
+                [
+                    "gcn_seqno" => "45190",
+                    "med_name_id" => "18604",
+                    "medid" => "234539",
+                    "routed_dosage_form_med_id" => "95130",
+                    "routed_med_id" => "19876",
+                    "rx" => "Zyprexa Zydis",
+                ],
+                [
+                    "gcn_seqno" => "49853",
+                    "med_name_id" => "26164",
+                    "medid" => "400058",
+                    "routed_dosage_form_med_id" => "36194",
+                    "routed_med_id" => "32562",
+                    "rx" => "Orfadin",
+                ],
+            ]));
+        }
+        else {
+            $input = json_decode($request->input('data'));
+            $rx = $input->rx;
+        }
+
+        if(count($rx) < 2) return "";
+
+        $leftIndex = 0;
+
+        $output = [];
+
+        for ($i=$leftIndex; $i<count($rx) - 1; $i++) {
+            for ($j=$i + 1; $j<count($rx); $j++) {
+                $output[] = $this->drugDrugInteractionSinglePair($rx[$i], $rx[$j]);
+            }
+        }
+
+        $output = array_filter($output, function($_x) {
+            return !!$_x;
+        });
+
+        return implode("<br>", $output);
+    }
+
+    private function drugDrugInteractionSinglePair($_rx1, $_rx2) {
+
+        $output = [];
+
+        // get active ingredient DDI_CODEX values for drug 1 and 2
+        $rx1ActiveDdiCodex = $this->getActiveDdiCodexFromGcnSeqNo($_rx1->gcn_seqno);
+        $rx2ActiveDdiCodex = $this->getActiveDdiCodexFromGcnSeqNo($_rx2->gcn_seqno);
+        if(!$rx1ActiveDdiCodex || !$rx2ActiveDdiCodex || !count($rx1ActiveDdiCodex) || !count($rx2ActiveDdiCodex)) return "";
+
+//        dump($rx1_DDI_CODEX);
+//        dump($rx2_DDI_CODEX);
+
+        // get inactive ingredient DDI_CODEX values for drug 1 and 2
+        // to get this we need to first get the NDCs of the drugs
+        $rx1Ndc = $this->getNdcFromMedId($_rx1->medid);
+        $rx2Ndc = $this->getNdcFromMedId($_rx2->medid);
+
+//        dump($rx1Ndc);
+//        dump($rx2Ndc);
+
+        $rx1InactiveDdiCodex = $this->getInactiveDdiCodexFromNdc($rx1Ndc);
+        $rx2InactiveDdiCodex = $this->getInactiveDdiCodexFromNdc($rx2Ndc);
+        // if(!$rx1InactiveDdiCodex || !$rx2InactiveDdiCodex || !count($rx1InactiveDdiCodex) || !count($rx2InactiveDdiCodex)) return "";
+
+//        dump($rx1InactiveDdiCodex);
+//        dump($rx2InactiveDdiCodex);
+
+        // get ddi codex - monox pairs for drug 1 & 2
+        $rx1ActiveDdiCodexMonoxPairs = $this->getDdiCodexMonoxPairs($rx1ActiveDdiCodex);
+        $rx1InactiveDdiCodexMonoxPairs = $this->getDdiCodexMonoxPairs($rx1InactiveDdiCodex);
+        $rx2ActiveDdiCodexMonoxPairs = $this->getDdiCodexMonoxPairs($rx2ActiveDdiCodex);
+        $rx2InactiveDdiCodexMonoxPairs = $this->getDdiCodexMonoxPairs($rx2InactiveDdiCodex);
+
+//        dump($rx1ActiveDdiCodexMonoxPairs);
+//        dump($rx1InactiveDdiCodexMonoxPairs);
+//        dump($rx2ActiveDdiCodexMonoxPairs);
+//        dump($rx2InactiveDdiCodexMonoxPairs);
+
+        // compare 1-active to 2-active and 2-inactive
+        $activeCatches = [];
+        foreach ($rx1ActiveDdiCodexMonoxPairs as $compareLeft) {
+            foreach ($rx2ActiveDdiCodexMonoxPairs as $compareRight) {
+                if($compareLeft->ddi_monox == $compareRight->ddi_monox && $compareLeft->ddi_codex != $compareRight->ddi_codex) {
+                    $activeCatches[] = $compareLeft->ddi_codex;
+                }
+            }
+            foreach ($rx2InactiveDdiCodexMonoxPairs as $compareRight) {
+                if($compareLeft->ddi_monox == $compareRight->ddi_monox && $compareLeft->ddi_codex != $compareRight->ddi_codex) {
+                    $activeCatches[] = $compareLeft->ddi_codex;
+                }
+            }
+        }
+
+        // compare 1-inactive to 2-active and 2-inactive
+        $inactiveCatches = [];
+        foreach ($rx1InactiveDdiCodexMonoxPairs as $compareLeft) {
+            foreach ($rx2ActiveDdiCodexMonoxPairs as $compareRight) {
+                if($compareLeft->ddi_monox == $compareRight->ddi_monox && $compareLeft->ddi_codex != $compareRight->ddi_codex) {
+                    $inactiveCatches[] = $compareLeft->ddi_codex;
+                }
+            }
+            foreach ($rx2InactiveDdiCodexMonoxPairs as $compareRight) {
+                if($compareLeft->ddi_monox == $compareRight->ddi_monox && $compareLeft->ddi_codex != $compareRight->ddi_codex) {
+                    $inactiveCatches[] = $compareLeft->ddi_codex;
+                }
+            }
+        }
+
+        if(count($activeCatches)) {
+            $output[] = "<b>{$_rx2->rx}</b> interacts with one or more active ingredients in <b>{$_rx1->rx}</b>.";
+        }
+        if(count($inactiveCatches)) {
+            $output[] = "<b>{$_rx2->rx}</b> interacts with one or more inactive ingredients in <b>{$_rx1->rx}</b>.";
+        }
+
+        // TODO: find out and show the names of the actual ingredients causing interaction
+
+        return implode("<br>", $output);
+    }
+
+    private function getActiveDdiCodexFromGcnSeqNo($_gcnSeqNo) {
+        $ddiCodexArray = [];
+        $query = DB::connection('pgsql_fdb')->select("
+SELECT r1.ddi_codex
+FROM RADIMGC4_GCNSEQNO_LINK r1 
+WHERE r1.gcn_seqno = :gcnSeqNo
+            ",
+            ['gcnSeqNo' => $_gcnSeqNo]
+        );
+        if(count($query)) {
+            $ddiCodexArray = array_map(function($_x) {
+                return $_x->ddi_codex;
+            }, $query);
+        }
+        return $ddiCodexArray;
+    }
+
+    private function getNdcFromMedId($_medId) {
+        $ndcArray = [];
+        $query = DB::connection('pgsql_fdb')->select("
+select ndc from rmindc1_ndc_medid where medid = :medId
+            ",
+            ['medId' => $_medId]
+        );
+        if(count($query)) {
+            $ndcArray = array_map(function($_x) {
+                return $_x->ndc;
+            }, $query);
+        }
+        return $ndcArray;
+    }
+
+    private function getInactiveDdiCodexFromNdc($_ndc) {
+        $ddiCodexArray = [];
+        $query = DB::connection('pgsql_fdb')->select("
+SELECT distinct r1.ddi_codex
+FROM RDDIMIN0_NDC_INACTV_DDIM_LINK r1 
+WHERE r1.ddi_ndc IN (" . implode(',', array_map(function($_x) {return "'" . $_x . "'";}, $_ndc)) . ")
+            "
+        );
+        if(count($query)) {
+            $ddiCodexArray = array_map(function($_x) {
+                return $_x->ddi_codex;
+            }, $query);
+        }
+        return $ddiCodexArray;
+    }
+
+    private function getDdiCodexMonoxPairs($_ddiCodexArray) {
+        $ddiCodexMonoxPairsArray = [];
+        if(count($_ddiCodexArray)) {
+            $ddiCodexMonoxPairsArray = DB::connection('pgsql_fdb')->select("
+SELECT r1.ddi_codex, r1.ddi_monox
+FROM RADIMMA5_MSTR r1 
+WHERE r1.ddi_codex IN (" . implode(',', array_map(function($_x) {return "'" . $_x . "'";}, $_ddiCodexArray)) . ")
+            "
+            );
+        }
+        return $ddiCodexMonoxPairsArray;
+    }
+
+    public function drugCoadministration(Request $request) {
+        $gcnSeqnos = $request->input('gcn-seqnos') ? trim($request->input('gcn-seqnos')) : '';
+        if (empty($gcnSeqnos)) return '';
+        //$gcnSeqnos = explode(",", $gcnSeqnos);
+
+        $coadministration = DB::connection('pgsql_fdb')->select("
+SELECT distinct r1.coadmin_dosing_text
+FROM radige0_ddi_gcnseqno_except r1 
+WHERE r1.side_a_gcn_seqno in ($gcnSeqnos) AND r1.side_b_gcn_seqno in ($gcnSeqnos)
+            "
+        );
+        return view('app.fdb-pg.fdb-coadministration', compact('coadministration'));
+    }
+
+    public function duplicateTherapy(Request $request) {
+
+        if($request->input('test')) {
+            // override
+            $rx = json_decode(json_encode([
+                [
+                    "gcn_seqno" => "4376",
+                    "med_name_id" => "1076",
+                    "medid" => "172480",
+                    "routed_dosage_form_med_id" => "5870",
+                    "routed_med_id" => "1082",
+                    "rx" => "aspirin 325",
+                ],
+                [
+                    "gcn_seqno" => "4377",
+                    "med_name_id" => "1076",
+                    "medid" => "216092",
+                    "routed_dosage_form_med_id" => "5870",
+                    "routed_med_id" => "1082",
+                    "rx" => "aspirin 500",
+                ],
+            ]));
+        }
+        else {
+            $input = json_decode($request->input('data'));
+            $rx = $input->rx;
+        }
+
+        $dptClasses = [];
+        foreach ($rx as $rxItem) {
+            $rxItem->dpt = $this->getDptClassFromGcnSeqNo($rxItem->gcn_seqno);
+        }
+
+        // dd($rx);
+
+        $leftIndex = 0;
+        $matches = [];
+        for ($i=$leftIndex; $i<count($rx) - 1; $i++) {
+            for ($j=$i + 1; $j<count($rx); $j++) {
+                $compareResult = $this->compareDPTs($rx[$i]->dpt, $rx[$j]->dpt);
+                foreach ($compareResult as $c) {
+                    $matches[] = "<b>{$rx[$i]->rx}</b> and <b>{$rx[$j]->rx}</b> both participate in the duplicate therapy class <b>{$c->dpt_class_desc}</b> (duplicates allowed: {$c->dpt_allowance})";
+                }
+            }
+        }
+
+        return "<ol class='pl-0 ml-3'>" . implode("", array_map(function($_x) {
+                return "<li class='mb-2'>" . $_x . "</li>";
+            }, $matches)) . "</ol>";
+    }
+
+    private function getDptClassFromGcnSeqNo($_gcnSeqNo) {
+        return DB::connection('pgsql_fdb')->select("
+SELECT distinct r1.dpt_class_id, r2.dpt_allowance, r2.dpt_class_desc
+FROM RDPTGC0_GCNSEQNO_LINK r1 
+JOIN RDPTCL0_CLASS_ID r2 on r1.dpt_class_id = r2.dpt_class_id
+WHERE r1.gcn_seqno = :gcnSeqNo
+            ",
+            ['gcnSeqNo' => $_gcnSeqNo]
+        );
+    }
+
+    private function compareDPTs($_dptArray1, $_dptArray2) {
+        $output = [];
+        for ($i=0; $i<count($_dptArray1); $i++) {
+            for ($j=0; $j<count($_dptArray2); $j++) {
+                if($_dptArray1[$i]->dpt_class_id == $_dptArray2[$j]->dpt_class_id) {
+                    $output[] = json_decode(json_encode([
+                        "dpt_allowance" => $_dptArray1[$i]->dpt_allowance,
+                        "dpt_class_desc" => $_dptArray1[$i]->dpt_class_desc
+                    ]));
+                }
+            }
+        }
+        return $output;
+    }
+}

+ 15 - 0
config/database.php

@@ -78,6 +78,21 @@ return [
             'sslmode' => 'prefer',
         ],
 
+        'pgsql_fdb' => [
+            'driver' => 'pgsql',
+            'url' => env('DATABASE_URL'),
+            'host' => env('DB_HOST_FDB', '127.0.0.1'),
+            'port' => env('DB_PORT_FDB', '5432'),
+            'database' => env('DB_DATABASE_FDB', 'forge'),
+            'username' => env('DB_USERNAME_FDB', 'forge'),
+            'password' => env('DB_PASSWORD_FDB', ''),
+            'charset' => 'utf8',
+            'prefix' => '',
+            'prefix_indexes' => true,
+            'schema' => 'public',
+            'sslmode' => 'prefer',
+        ],
+
         'sqlsrv' => [
             'driver' => 'sqlsrv',
             'url' => env('DATABASE_URL'),

+ 12 - 0
resources/views/app/fdb-pg/fdb-allergy-suggest.blade.php

@@ -0,0 +1,12 @@
+@if(!count($matches))
+    <span class="d-block no-suggest-items">No matches!</span>
+@endif
+<?php $activeSet = false; ?>
+@foreach($matches as $match)
+    <a native class="d-block suggest-item fdb-suggest text-nowrap {{ $activeSet ? '' : 'active'  }}" href="#"
+       data-dam-concept-id="{{$match->dam_concept_id}}" data-dam-concept-id-typ="{{$match->dam_concept_id_typ}}">
+        {{$match->dam_concept_id_desc}} <span class="text-sm">({{$match->dam_concept_id_typ == 6 ? 'ingredient' : ($match->dam_concept_id_typ == 1 ? 'allergen group' : 'medication')}})</span>
+    </a>
+    <?php $activeSet = true; ?>
+@endforeach
+

+ 16 - 0
resources/views/app/fdb-pg/fdb-coadministration.blade.php

@@ -0,0 +1,16 @@
+@if(count($coadministration))
+    <div class="d-flex px-2 py-1 bg-white border-bottom align-items-baseline">
+        <span>Count: <b>{{count($coadministration)}}</b></span>
+        <a href="#" class="text-sm ml-auto" onclick="$(this).parent().next('table').toggle(); return false;">Toggle</a>
+    </div>
+    <table class="table table-sm table-striped table-bordered" style="display: none">
+        <tbody>
+        @foreach($coadministration as $item)
+            <tr>
+                <td>{{$item->coadmin_dosing_text}}</td>
+            </tr>
+        @endforeach
+        </tbody>
+    </table>
+@endif
+

+ 25 - 0
resources/views/app/fdb-pg/fdb-contraindications.blade.php

@@ -0,0 +1,25 @@
+@if(!count($contraindications))
+    <span class="d-block no-suggest-items">No contraindications!</span>
+@else
+    <div class="d-flex px-2 py-1 bg-white border-bottom align-items-baseline">
+        <span>Count: <b>{{count($contraindications)}}</b></span>
+        <a href="#" class="text-sm ml-auto" onclick="$(this).parent().next('table').toggle(); return false;">Toggle</a>
+    </div>
+    <table class="table table-sm table-striped table-bordered" style="display: none">
+        <thead>
+        <tr>
+            <th>Severity</th>
+            <th>Contraindication</th>
+        </tr>
+        </thead>
+        <tbody>
+        @foreach($contraindications as $contraindication)
+            <tr>
+                <td class="font-weight-bold">{{$contraindication->ddxcn_sl == 1 ? 'Contraindication' : ($contraindication->ddxcn_sl == 2 ? 'Severe Warning' : 'Moderate Warning')}}</td>
+                <td>{{$contraindication->dxid_desc56}}</td>
+            </tr>
+        @endforeach
+        </tbody>
+    </table>
+@endif
+

+ 12 - 0
resources/views/app/fdb-pg/fdb-dx-suggest.blade.php

@@ -0,0 +1,12 @@
+@if(!count($matches))
+    <span class="d-block no-suggest-items">No matches!</span>
+@endif
+<?php $activeSet = false; ?>
+@foreach($matches as $match)
+    <a native class="d-block suggest-item fdb-suggest text-nowrap {{ $activeSet ? '' : 'active'  }}" href="#"
+       data-dx-id="{{$match->dxid}}">
+        {{$match->dxid_desc56}}
+    </a>
+    <?php $activeSet = true; ?>
+@endforeach
+

+ 27 - 0
resources/views/app/fdb-pg/fdb-geriatric-precautions.blade.php

@@ -0,0 +1,27 @@
+@if(!count($precautions))
+    <span class="d-block no-suggest-items">No geriatric precautions!</span>
+@else
+    <div class="d-flex px-2 py-1 bg-white border-bottom align-items-baseline">
+        <span>Count: <b>{{count($precautions)}}</b></span>
+        <a href="#" class="text-sm ml-auto" onclick="$(this).parent().next('table').toggle(); return false;">Toggle</a>
+    </div>
+    <table class="table table-sm table-striped table-bordered" style="display: none">
+        <thead>
+        <tr>
+            <th>Desc</th>
+            <th>Severity</th>
+            <th>Narrative</th>
+        </tr>
+        </thead>
+        <tbody>
+        @foreach($precautions as $precaution)
+            <tr>
+                <td>{{$precaution->geri_desc}}</td>
+                <td>{{$precaution->geri_sl == 1 ? 'Contraindication' : 'Management or Monitoring Precaution'}}</td>
+                <td>{{$precaution->geri_narrative}}</td>
+            </tr>
+        @endforeach
+        </tbody>
+    </table>
+@endif
+

+ 25 - 0
resources/views/app/fdb-pg/fdb-indications.blade.php

@@ -0,0 +1,25 @@
+@if(!count($indications))
+    <span class="d-block no-suggest-items">No indications!</span>
+@else
+    <div class="d-flex px-2 py-1 bg-white border-bottom align-items-baseline">
+        <span>Count: <b>{{count($indications)}}</b></span>
+        <a href="#" class="text-sm ml-auto" onclick="$(this).parent().next('table').toggle(); return false;">Toggle</a>
+    </div>
+    <table class="table table-sm table-striped table-bordered" style="display: none">
+        <thead>
+        <tr>
+            <th>Indication</th>
+            <th>FDA Approved</th>
+        </tr>
+        </thead>
+        <tbody>
+        @foreach($indications as $indication)
+            <tr>
+                <td class="font-weight-bold">{{$indication->dxid_desc56}}</td>
+                <td>{{$indication->indcts_lbl}}</td>
+            </tr>
+        @endforeach
+        </tbody>
+    </table>
+@endif
+

+ 12 - 0
resources/views/app/fdb-pg/fdb-med-suggest.blade.php

@@ -0,0 +1,12 @@
+@if(!count($matches))
+    <span class="d-block no-suggest-items">No matches!</span>
+@endif
+<?php $activeSet = false; ?>
+@foreach($matches as $match)
+    <a native class="d-block suggest-item fdb-suggest text-nowrap {{ $activeSet ? '' : 'active'  }}" href="#"
+       data-med-name-id="{{$match->med_name_id}}">
+        {{$match->med_name}}
+    </a>
+    <?php $activeSet = true; ?>
+@endforeach
+

+ 667 - 0
resources/views/app/fdb-pg/fdb-rx.blade.php

@@ -0,0 +1,667 @@
+@extends ('layouts/template')
+
+@section('content')
+
+    <style>
+      .fdb-suggestions-container .suggestions-outer {
+        right: auto;
+        left: 0;
+      }
+    </style>
+
+    <div class="p-0 mcp-theme-1" id="fdb-pg">
+        <div class="px-3 py-2 bg-light border-bottom d-flex align-items-center font-weight-bold font-size-14">FDB Playground</div>
+        <div class="p-3 bg-light">
+            <div class="row mb-3">
+                <div class="col-6">
+                    <div class="bg-white p-3 border">
+                        <div class="d-flex align-items-baseline mb-2">
+                            <span class="font-size-14 font-weight-bold">Allergies</span>
+                            <a href="#" v-on:click.prevent="addAllergy()" class="ml-3">+ Add</a>
+                        </div>
+                        <table class="table table-sm table-striped table-bordered m-0">
+                        <thead>
+                        <tr>
+                            <th class="border-bottom-0 text-secondary">Allergy</th>
+                            <th class="border-bottom-0 width-100px"></th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        <tr v-for="(item, index) in allergies">
+                            <td class="p-0 position-relative">
+                                <input type="text" class="rounded-0 border-0 form-control form-control-sm min-width-unset"
+                                       fdb-allergy-suggest-search :fdb-suggest-group="index">
+                            </td>
+                            <td class="text-center">
+                                <a href="#" v-on:click.prevent="allergies.splice(index, 1); showDrugAllergyNotes();"><i class="fa fa-trash-alt text-danger"></i></a>
+                            </td>
+                        </tr>
+                        </tbody>
+                    </table>
+                    </div>
+                </div>
+                <div class="col-6 pl-0">
+                    <div class="bg-white p-3 border">
+                        <div class="d-flex align-items-baseline mb-2">
+                            <span class="font-size-14 font-weight-bold">Current Problems</span>
+                            <a href="#" v-on:click.prevent="addDx()" class="ml-3">+ Add</a>
+                        </div>
+                        <table class="table table-sm table-striped table-bordered m-0">
+                        <thead>
+                        <tr>
+                            <th class="border-bottom-0 text-secondary">Problem</th>
+                            <th class="border-bottom-0 width-100px"></th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        <tr v-for="(item, index) in dx">
+                            <td class="p-0 position-relative">
+                                <input type="text" class="rounded-0 border-0 form-control form-control-sm min-width-unset"
+                                       fdb-dx-suggest-search :fdb-suggest-group="index">
+                            </td>
+                            <td class="text-center">
+                                <a href="#" v-on:click.prevent="dx.splice(index, 1)"><i class="fa fa-trash-alt text-danger"></i></a>
+                            </td>
+                        </tr>
+                        </tbody>
+                    </table>
+                    </div>
+                </div>
+            </div>
+            <div class="bg-white p-3 mb-3 border">
+                <div class="d-flex align-items-baseline mb-2">
+                    <span class="font-size-14 font-weight-bold">Medications</span>
+                    <a href="#" v-on:click.prevent="addRx()" class="ml-3">+ Add</a>
+                </div>
+                <table class="table table-sm table-bordered m-0" style="table-layout: fixed">
+                <thead>
+                <tr>
+                    <th class="border-bottom-0 text-secondary">Medication</th>
+                    <th class="border-bottom-0 text-secondary">Route</th>
+                    <th class="border-bottom-0 text-secondary">Dosage</th>
+                    <th class="border-bottom-0 text-secondary">Strength</th>
+                    <th class="border-bottom-0 text-secondary">Side Effects</th>
+                    <th class="border-bottom-0 text-secondary">Ger. Precautions</th>
+                    <th class="border-bottom-0 text-secondary">Indications</th>
+                    <th class="border-bottom-0 text-secondary">Contraindications</th>
+                    <th class="border-bottom-0 width-100px"></th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr v-for="(item, index) in rx">
+                    <td class="p-0 position-relative">
+                        <input type="text" class="rounded-0 border-0 form-control form-control-sm min-width-unset"
+                               fdb-med-suggest-search :fdb-suggest-group="index">
+                    </td>
+                    <td class="p-0 position-relative">
+                        <select class="rounded-0 border-0 form-control form-control-sm min-width-unset"
+                                fdb-med-suggest-route :fdb-suggest-group="index" v-model="item.routed_med_id"
+                                disabled>
+                        </select>
+                    </td>
+                    <td class="p-0 position-relative">
+                        <select class="rounded-0 border-0 form-control form-control-sm min-width-unset"
+                                fdb-med-suggest-dosage :fdb-suggest-group="index" v-model="item.routed_dosage_form_med_id"
+                                disabled>
+                        </select>
+                    </td>
+                    <td class="p-0 position-relative">
+                        <select class="rounded-0 border-0 form-control form-control-sm min-width-unset"
+                                fdb-med-suggest-strength :fdb-suggest-group="index" v-model="item.gcn_seqno"
+                                disabled>
+                        </select>
+                    </td>
+
+                    <td class="p-0 overflow-auto side-effects" :fdb-suggest-group="index">
+
+                    </td>
+                    <td class="p-0 overflow-auto geriatric-precautions" :fdb-suggest-group="index">
+
+                    </td>
+                    <td class="p-0 overflow-auto indications" :fdb-suggest-group="index">
+
+                    </td>
+                    <td class="p-0 overflow-auto contraindications" :fdb-suggest-group="index">
+
+                    </td>
+                    <td class="text-center">
+                        <a href="#" v-on:click.prevent="rx.splice(index, 1); showDrugAllergyNotes(); showDrugCoadministrationNotes(); showDuplicateTherapyNotes()"><i class="fa fa-trash-alt text-danger"></i></a>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+            </div>
+            <div class="row">
+                <div class="col-3">
+                    <div class="bg-white p-3 border">
+                        <div class="d-flex align-items-baseline mb-2">
+                            <span class="font-size-14 font-weight-bold">Drug Allergy Notes</span>
+                        </div>
+                        <div class="drug-allergies"></div>
+                    </div>
+                </div>
+                <div class="col-3 pl-0">
+                    <div class="bg-white p-3 border">
+                        <div class="d-flex align-items-baseline mb-2">
+                            <span class="font-size-14 font-weight-bold">Drug Drug Interaction Notes</span>
+                        </div>
+                        <div class="drug-drug-interaction"></div>
+                    </div>
+                </div>
+                <div class="col-3 pl-0">
+                    <div class="bg-white p-3 border">
+                        <div class="d-flex align-items-baseline mb-2">
+                            <span class="font-size-14 font-weight-bold">Duplicate Therapy Notes</span>
+                        </div>
+                        <div class="duplicate-therapy"></div>
+                    </div>
+                </div>
+                <div class="col-3 pl-0">
+                    <div class="bg-white p-3 border">
+                        <div class="d-flex align-items-baseline mb-2">
+                            <span class="font-size-14 font-weight-bold">Coadministration Notes</span>
+                        </div>
+                        <div class="drug-coadministration"></div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <script>
+        (function() {
+
+            let suggestionsOuter = null;
+
+            const debounce = (func, wait) => {
+                let timeout;
+                return function executedFunction(...args) {
+                    const later = () => {
+                        clearTimeout(timeout);
+                        func(...args);
+                    };
+                    clearTimeout(timeout);
+                    timeout = setTimeout(later, wait);
+                };
+            };
+
+            var lastTerm = '';
+            var returnedFunction = debounce(function (elem) {
+                let term = elem.val();
+                if (!!term && lastTerm !== term) {
+                    let ep = false;
+                    if($(elem).is('[fdb-med-suggest-search]')) {
+                        ep = '/fdb-med-suggest';
+                    }
+                    else if($(elem).is('[fdb-dx-suggest-search]')) {
+                        ep = '/fdb-dx-suggest';
+                    }
+                    else if($(elem).is('[fdb-allergy-suggest-search]')) {
+                        ep = '/fdb-allergy-suggest';
+                    }
+                    $.get(ep + '?term=' + $.trim(term), function (_data) {
+                        suggestionsOuter.html(_data).removeClass('d-none');
+                    });
+                    lastTerm = term;
+                } else {
+                    suggestionsOuter.addClass('d-none');
+                }
+            }, 250);
+
+            function handleKeydown(elem, e) {
+                let term = $.trim(elem.val());
+                let activeItem = suggestionsOuter.find('.suggest-item.active');
+                switch (e.which) {
+                    case 27:
+                        suggestionsOuter.addClass('d-none');
+                        return false;
+                    case 38:
+                        if (activeItem.prev().length) {
+                            activeItem.prev()
+                                .addClass('active')
+                                .siblings().removeClass('active');
+                            activeItem = suggestionsOuter.find('.suggest-item.active');
+                            if (activeItem.length) {
+                                activeItem[0].scrollIntoView();
+                            }
+                        }
+                        return false;
+                    case 40:
+                        if (activeItem.next().length) {
+                            activeItem.next()
+                                .addClass('active')
+                                .siblings().removeClass('active');
+                            activeItem = suggestionsOuter.find('.suggest-item.active');
+                            if (activeItem.length) {
+                                activeItem[0].scrollIntoView();
+                            }
+                        }
+                        return false;
+                    case 13:
+                        if (activeItem.length) {
+                            activeItem.first().click();
+                        }
+                        return false;
+                    default:
+                        if (!!term) {
+                            suggestionsOuter
+                                .html('<span class="d-block no-suggest-items">Searching...</span>')
+                                .removeClass('d-none');
+                            returnedFunction(elem);
+                        } else {
+                            suggestionsOuter.addClass('d-none');
+                        }
+                        break;
+                }
+            }
+
+            function handleKeypress(elem, e) {
+                var term = $.trim(elem.val());
+                if (!!term) {
+                    suggestionsOuter
+                        .html('<span class="d-block no-suggest-items">Searching...</span>')
+                        .removeClass('d-none');
+                    returnedFunction(elem);
+                } else {
+                    suggestionsOuter.addClass('d-none');
+                }
+            }
+
+            function fillSideEffects(_group, _gcn_seqno) {
+                let container = $('.side-effects[fdb-suggest-group=' + _group + ']');
+                container.empty();
+                $.get('/fdb-side-effects?gcn-seq-no=' + _gcn_seqno, _data => {
+                    container.html(_data);
+                });
+            }
+
+            function fillGeriatricPrecautions(_group, _gcn_seqno) {
+                let container = $('.geriatric-precautions[fdb-suggest-group=' + _group + ']');
+                container.empty();
+                $.get('/fdb-geriatric-precautions?gcn-seq-no=' + _gcn_seqno, _data => {
+                    container.html(_data);
+                });
+            }
+
+            function fillIndications(_group, _gcn_seqno) {
+                let container = $('.indications[fdb-suggest-group=' + _group + ']');
+                container.empty();
+                $.get('/fdb-indications?gcn-seq-no=' + _gcn_seqno, _data => {
+                    container.html(_data);
+                });
+            }
+
+            function fillContraindications(_group, _gcn_seqno) {
+                let container = $('.contraindications[fdb-suggest-group=' + _group + ']');
+                container.empty();
+                $.get('/fdb-contraindications?routed-med-id=' + _gcn_seqno, _data => {
+                    container.html(_data);
+                });
+            }
+
+            addMCInitializer('fdb-pg', function() {
+
+                window.fdbPGApp = new Vue({
+                    el: '#fdb-pg',
+                    delimiters: ['@{{', '}}'],
+                    data: {
+                        allergies: [{
+
+                        }],
+                        dx: [{
+
+                        }],
+                        rx: [{
+
+                        }],
+                    },
+                    methods: {
+                        addRx: function() {
+                            this.rx.push({});
+                            Vue.nextTick(() => {
+                                this.initFDBRxSuggest();
+                                $('[fdb-med-suggest-search]').last().focus();
+                            });
+                        },
+                        addDx: function() {
+                            this.dx.push({});
+                            Vue.nextTick(() => {
+                                this.initFDBDxSuggest();
+                                $('[fdb-dx-suggest-search]').last().focus();
+                            });
+                        },
+                        addAllergy: function() {
+                            this.allergies.push({});
+                            Vue.nextTick(() => {
+                                this.initFDBAllergySuggest();
+                                $('[fdb-allergy-suggest-search]').last().focus();
+                            });
+                        },
+                        showDrugAllergyNotes: function() {
+                            $('.drug-allergies').html('');
+                            if(this.allergies && this.allergies.length && this.rx && this.rx.length) {
+                                $.get('/fdb-drug-allergies', {
+                                    data: JSON.stringify({
+                                        allergies: this.allergies,
+                                        rx: this.rx
+                                    })
+                                }, _data => {
+                                    $('.drug-allergies').html(_data);
+                                });
+                            }
+                        },
+                        showDrugDrugInteractionNotes: function() {
+                            $('.drug-drug-interaction').html('');
+                            if(this.rx && this.rx.length > 1) {
+                                $.get('/fdb-drug-drug-interaction', {
+                                    data: JSON.stringify({
+                                        rx: this.rx
+                                    })
+                                }, _data => {
+                                    $('.drug-drug-interaction').html(_data);
+                                });
+                            }
+                        },
+                        showDrugCoadministrationNotes: function() {
+                            $('.drug-coadministration').html('');
+                            if(this.rx && this.rx.length > 1) {
+                                let gcnSeqNos = [];
+                                for (let i = 0; i < this.rx.length; i++) {
+                                    gcnSeqNos.push(this.rx[i].gcn_seqno);
+                                }
+                                gcnSeqNos = gcnSeqNos.join(",");
+                                $.get('/fdb-drug-coadministration', {
+                                    'gcn-seqnos': gcnSeqNos
+                                }, _data => {
+                                    $('.drug-coadministration').html(_data);
+                                });
+                            }
+                        },
+                        showDuplicateTherapyNotes: function() {
+                            $('.duplicate-therapy').html('');
+                            if(this.rx && this.rx.length > 1) {
+                                $.get('/fdb-duplicate-therapy', {
+                                    data: JSON.stringify({
+                                        rx: this.rx
+                                    })
+                                }, _data => {
+                                    $('.duplicate-therapy').html(_data);
+                                });
+                            }
+                        },
+                        initFDBRxSuggest: function() {
+                            $('[fdb-med-suggest-search]:not([fdb-suggest-initialized])').each(function() {
+                                let elem = $(this);
+                                elem.next('.fdb-suggestions-container').remove();
+                                $('<div class="fdb-suggestions-container position-relative" fdb-suggest-group="' + elem.attr('fdb-suggest-group') + '">' +
+                                    '<div class="suggestions-outer fdb-suggestions position-absolute d-none"></div>' +
+                                    '</div>').insertAfter(elem);
+
+                                elem
+                                    .off('keydown.fdb-suggest')
+                                    .on('keydown.fdb-suggest', function (e) {
+                                        suggestionsOuter = $(this).next('.fdb-suggestions-container').find('>.suggestions-outer');
+                                        return handleKeydown($(this), e);
+                                    })
+                                    .off('keypress.fdb-suggest')
+                                    .on('keypress.fdb-suggest', function (e) {
+                                        suggestionsOuter = $(this).next('.fdb-suggestions-container').find('>.suggestions-outer');
+                                        return handleKeypress($(this), e);
+                                    });
+
+                                $(this).attr('fdb-suggest-initialized', 1);
+                            });
+                        },
+                        initFDBDxSuggest: function() {
+                            $('[fdb-dx-suggest-search]:not([fdb-suggest-initialized])').each(function() {
+                                let elem = $(this);
+                                elem.next('.fdb-suggestions-container').remove();
+                                $('<div class="fdb-suggestions-container position-relative" fdb-suggest-group="' + elem.attr('fdb-suggest-group') + '">' +
+                                    '<div class="suggestions-outer fdb-suggestions position-absolute d-none"></div>' +
+                                    '</div>').insertAfter(elem);
+
+                                elem
+                                    .off('keydown.fdb-suggest')
+                                    .on('keydown.fdb-suggest', function (e) {
+                                        suggestionsOuter = $(this).next('.fdb-suggestions-container').find('>.suggestions-outer');
+                                        return handleKeydown($(this), e);
+                                    })
+                                    .off('keypress.fdb-suggest')
+                                    .on('keypress.fdb-suggest', function (e) {
+                                        suggestionsOuter = $(this).next('.fdb-suggestions-container').find('>.suggestions-outer');
+                                        return handleKeypress($(this), e);
+                                    });
+
+                                $(this).attr('fdb-suggest-initialized', 1);
+                            });
+                        },
+                        initFDBAllergySuggest: function() {
+                            $('[fdb-allergy-suggest-search]:not([fdb-suggest-initialized])').each(function() {
+                                let elem = $(this);
+                                elem.next('.fdb-suggestions-container').remove();
+                                $('<div class="fdb-suggestions-container position-relative" fdb-suggest-group="' + elem.attr('fdb-suggest-group') + '">' +
+                                    '<div class="suggestions-outer fdb-suggestions position-absolute d-none"></div>' +
+                                    '</div>').insertAfter(elem);
+
+                                elem
+                                    .off('keydown.fdb-suggest')
+                                    .on('keydown.fdb-suggest', function (e) {
+                                        suggestionsOuter = $(this).next('.fdb-suggestions-container').find('>.suggestions-outer');
+                                        return handleKeydown($(this), e);
+                                    })
+                                    .off('keypress.fdb-suggest')
+                                    .on('keypress.fdb-suggest', function (e) {
+                                        suggestionsOuter = $(this).next('.fdb-suggestions-container').find('>.suggestions-outer');
+                                        return handleKeypress($(this), e);
+                                    });
+
+                                $(this).attr('fdb-suggest-initialized', 1);
+                            });
+                        }
+                    },
+                    mounted: function() {
+
+                        let vueApp = this;
+
+                        // on auto-suggest med selection
+                        $(document).off('click', '.suggest-item.fdb-suggest[data-med-name-id]');
+                        $(document).on('click', '.suggest-item.fdb-suggest[data-med-name-id]', function () {
+
+                            $('.suggestions-outer.fdb-suggestions').addClass('d-none');
+
+                            let medNameID = $(this).attr('data-med-name-id'),
+                                label = $.trim($(this).text()),
+                                group = $(this).closest('.fdb-suggestions-container').attr('fdb-suggest-group');
+
+                            // set value
+                            let input = $(this).closest('.position-relative').prev('[fdb-med-suggest-search]');
+                            input.val(label);
+                            input.attr('data-med-name-id', medNameID);
+                            input.trigger('input');
+                            input.trigger('change');
+
+                            // vue value
+                            vueApp.rx[group].rx = label;
+                            vueApp.rx[group].med_name_id = medNameID;
+
+                            $(document).trigger('fdb-med-changed', [group]);
+
+                            return false;
+                        });
+
+                        // on auto-suggest dx selection
+                        $(document).off('click', '.suggest-item.fdb-suggest[data-dx-id]');
+                        $(document).on('click', '.suggest-item.fdb-suggest[data-dx-id]', function () {
+
+                            $('.suggestions-outer.fdb-suggestions').addClass('d-none');
+
+                            let dxID = $(this).attr('data-dx-id'),
+                                label = $.trim($(this).text()),
+                                group = $(this).closest('.fdb-suggestions-container').attr('fdb-suggest-group');
+
+                            // set value
+                            let input = $(this).closest('.position-relative').prev('[fdb-dx-suggest-search]');
+                            input.val(label);
+                            input.attr('data-dx-id', dxID);
+                            input.trigger('input');
+                            input.trigger('change');
+
+                            // vue value
+                            vueApp.dx[group].problem = label;
+                            vueApp.dx[group].dxid = dxID;
+
+                            $(document).trigger('fdb-dx-changed', [group]);
+
+                            return false;
+                        });
+
+                        // on auto-suggest allergy selection
+                        $(document).off('click', '.suggest-item.fdb-suggest[data-dam-concept-id]');
+                        $(document).on('click', '.suggest-item.fdb-suggest[data-dam-concept-id]', function () {
+
+                            $('.suggestions-outer.fdb-suggestions').addClass('d-none');
+
+                            let damConceptID = $(this).attr('data-dam-concept-id'),
+                                damConceptIDTyp = $(this).attr('data-dam-concept-id-typ'),
+                                label = $.trim($(this).text()),
+                                group = $(this).closest('.fdb-suggestions-container').attr('fdb-suggest-group');
+
+                            // set value
+                            let input = $(this).closest('.position-relative').prev('[fdb-allergy-suggest-search]');
+                            input.val(label);
+                            input.attr('data-dam-concept-id', damConceptID);
+                            input.attr('data-dam-concept-id-type', damConceptIDTyp);
+                            input.trigger('input');
+                            input.trigger('change');
+
+                            // vue value
+                            vueApp.allergies[group].allergen = label;
+                            vueApp.allergies[group].dam_concept_id = damConceptID;
+                            vueApp.allergies[group].dam_concept_id_typ = damConceptIDTyp;
+
+                            $(document).trigger('fdb-allergy-changed', [group]);
+
+                            vueApp.showDrugAllergyNotes();
+
+                            return false;
+                        });
+
+                        // med changed, update routes
+                        $(document).off('fdb-med-changed');
+                        $(document).on('fdb-med-changed', function(e, group) {
+
+                            // clear vue values
+                            vueApp.rx[group].routed_med_id = null;
+                            vueApp.rx[group].routed_dosage_form_med_id = null;
+                            vueApp.rx[group].medid = null;
+                            vueApp.rx[group].gcn_seqno = null;
+
+                            let routeSelect = $('[fdb-med-suggest-route][fdb-suggest-group='+group+']').empty().prop('disabled', true);
+                            let dosageSelect = $('[fdb-med-suggest-dosage][fdb-suggest-group='+group+']').empty().prop('disabled', true);
+                            let strengthSelect = $('[fdb-med-suggest-strength][fdb-suggest-group='+group+']').empty().prop('disabled', true);
+                            let medNameID = $('[fdb-med-suggest-search][fdb-suggest-group='+group+']').attr('data-med-name-id');
+                            $.get('/fdb-routed-meds?med-name-id=' + medNameID, _data => {
+                                routeSelect
+                                    .empty()
+                                    .append('<option value="">-- select --</option>');
+                                for (let i = 0; i < _data.length; i++) {
+                                    routeSelect.append('<option value="' + _data[i].routed_med_id + '">' + _data[i].med_routed_med_id_desc + '</option>')
+                                }
+                                routeSelect.prop('disabled', false);
+                                if(_data.length === 1) {
+                                    vueApp.rx[group].routed_med_id = _data[0].routed_med_id;
+                                    routeSelect.val(_data[0].routed_med_id).trigger('change');
+                                }
+                            }, 'json');
+                            routeSelect.prop('disabled', false);
+                        });
+
+                        // on route selection
+                        $(document).off('change', '[fdb-med-suggest-route]');
+                        $(document).on('change', '[fdb-med-suggest-route]', function () {
+                            let group = $(this).attr('fdb-suggest-group');
+
+                            // clear vue values
+                            vueApp.rx[group].routed_dosage_form_med_id = null;
+                            vueApp.rx[group].medid = null;
+                            vueApp.rx[group].gcn_seqno = null;
+
+                            let dosageSelect = $('[fdb-med-suggest-dosage][fdb-suggest-group='+group+']').empty().prop('disabled', true);
+                            let strengthSelect = $('[fdb-med-suggest-strength][fdb-suggest-group='+group+']').empty().prop('disabled', true);
+                            let routedMedID = $('[fdb-med-suggest-route][fdb-suggest-group='+$(this).attr('fdb-suggest-group')+']').val();
+                            $.get('/fdb-routed-dosages?routed-med-id=' + routedMedID, _data => {
+                                dosageSelect
+                                    .empty()
+                                    .append('<option value="">-- select --</option>');
+                                for (let i = 0; i < _data.length; i++) {
+                                    dosageSelect.append('<option value="' + _data[i].routed_dosage_form_med_id + '">' + _data[i].med_routed_df_med_id_desc + '</option>')
+                                }
+                                dosageSelect.prop('disabled', false);
+                                if(_data.length === 1) {
+                                    vueApp.rx[group].routed_dosage_form_med_id = _data[0].routed_dosage_form_med_id;
+                                    dosageSelect.val(_data[0].routed_dosage_form_med_id).trigger('change');
+                                }
+                            }, 'json');
+                        });
+
+                        // on dosage selection
+                        $(document).off('change', '[fdb-med-suggest-dosage]');
+                        $(document).on('change', '[fdb-med-suggest-dosage]', function () {
+                            let group = $(this).attr('fdb-suggest-group');
+
+                            vueApp.rx[group].medid = null;
+                            vueApp.rx[group].gcn_seqno = null;
+
+                            let strengthSelect = $('[fdb-med-suggest-strength][fdb-suggest-group='+group+']').empty().prop('disabled', true);
+                            let dosageFormMedID = $('[fdb-med-suggest-dosage][fdb-suggest-group='+$(this).attr('fdb-suggest-group')+']').val();
+                            $.get('/fdb-meds?dosage-form-med-id=' + dosageFormMedID, _data => {
+                                strengthSelect
+                                    .empty()
+                                    .append('<option value="">-- select --</option>');
+                                for (let i = 0; i < _data.length; i++) {
+                                    strengthSelect.append('<option data-medid="' + _data[i].medid + '" value="' + _data[i].gcn_seqno + '">' + _data[i].med_medid_desc + '</option>')
+                                }
+                                strengthSelect.prop('disabled', false);
+                                if(_data.length === 1) {
+                                    vueApp.rx[group].medid = _data[0].medid;
+                                    vueApp.rx[group].gcn_seqno = _data[0].gcn_seqno;
+                                    strengthSelect.val(_data[0].gcn_seqno).trigger('change');
+                                }
+                            }, 'json');
+                        });
+
+                        // on strength (medid) selection
+                        $(document).off('change', '[fdb-med-suggest-strength]');
+                        $(document).on('change', '[fdb-med-suggest-strength]', function () {
+
+                            let group = $(this).attr('fdb-suggest-group');
+                            vueApp.rx[group].medid = $(this).find('option:selected').attr('data-medid');
+
+                            fillSideEffects($(this).attr('fdb-suggest-group'), $(this).val());
+                            fillGeriatricPrecautions($(this).attr('fdb-suggest-group'), $(this).val());
+                            fillIndications($(this).attr('fdb-suggest-group'), $(this).val());
+
+                            let routedMedID = $('[fdb-med-suggest-route][fdb-suggest-group='+$(this).attr('fdb-suggest-group')+']').val();
+                            fillContraindications($(this).attr('fdb-suggest-group'), routedMedID);
+
+                            vueApp.showDrugAllergyNotes();
+                            vueApp.showDrugDrugInteractionNotes();
+                            vueApp.showDuplicateTherapyNotes();
+                            vueApp.showDrugCoadministrationNotes();
+                        });
+
+                        Vue.nextTick(() => {
+                            this.initFDBAllergySuggest();
+                            this.initFDBDxSuggest();
+                            this.initFDBRxSuggest();
+                            $('[fdb-allergy-suggest-search]').first().focus();
+                        });
+
+                    }
+                })
+
+            }, '#fdb-pg');
+        }).call(window);
+    </script>
+
+@endsection

+ 27 - 0
resources/views/app/fdb-pg/fdb-side-effects.blade.php

@@ -0,0 +1,27 @@
+@if(!count($sides))
+    <span class="d-block no-suggest-items">No side effects!</span>
+@else
+    <div class="d-flex px-2 py-1 bg-white border-bottom align-items-baseline">
+        <span>Count: <b>{{count($sides)}}</b></span>
+        <a href="#" class="text-sm ml-auto" onclick="$(this).parent().next('table').toggle(); return false;">Toggle</a>
+    </div>
+    <table class="table table-sm table-striped table-bordered" style="display: none">
+        <thead>
+        <tr>
+            <th>Side Effect</th>
+            <th>Severity</th>
+            <th>Frequency</th>
+        </tr>
+        </thead>
+        <tbody>
+        @foreach($sides as $side)
+            <tr>
+                <td class="font-weight-bold">{{$side->dxid_desc56}}</td>
+                <td>{{$side->side_sev == 0 ? 'Not Severe' : 'Severe'}}</td>
+                <td>{{$side->side_sev == 0 ? 'High' : ($side->side_sev == 1 ? 'Low' : 'Rare')}}</td>
+            </tr>
+        @endforeach
+        </tbody>
+    </table>
+@endif
+

+ 17 - 0
routes/web.php

@@ -368,6 +368,23 @@ Route::middleware('pro.auth')->group(function () {
     Route::post("/back_to_admin_pro", 'HomeController@backToAdminPro')->name('back-to-admin-pro');
 
     Route::get('/remote-monitoring-measurements/{careMonth}', 'PracticeManagementController@remoteMonitoringMeasurements')->name('remote-monitoring-measurements');
+
+    // fdb playground
+    Route::get('/fdb-pg-rx', 'FDBPGController@rx')->name('fdb-pg-rx');
+    Route::get('/fdb-med-suggest', 'FDBPGController@medSuggest');
+    Route::get('/fdb-routed-meds', 'FDBPGController@routedMeds');
+    Route::get('/fdb-routed-dosages', 'FDBPGController@routedDosages');
+    Route::get('/fdb-meds', 'FDBPGController@meds');
+    Route::get('/fdb-side-effects', 'FDBPGController@sideEffects');
+    Route::get('/fdb-geriatric-precautions', 'FDBPGController@geriatricPrecautions');
+    Route::get('/fdb-indications', 'FDBPGController@indications');
+    Route::get('/fdb-contraindications', 'FDBPGController@contraindications');
+    Route::get('/fdb-dx-suggest', 'FDBPGController@dxSuggest');
+    Route::get('/fdb-allergy-suggest', 'FDBPGController@allergySuggest');
+    Route::any('/fdb-drug-allergies', 'FDBPGController@drugAllergies');
+    Route::any('/fdb-drug-drug-interaction', 'FDBPGController@drugDrugInteraction');
+    Route::any('/fdb-drug-coadministration', 'FDBPGController@drugCoadministration');
+    Route::any('/fdb-duplicate-therapy', 'FDBPGController@duplicateTherapy');
 });
 
 Route::post("/process_form_submit", 'NoteController@processFormSubmit')->name('process_form_submit');