Pārlūkot izejas kodu

Merge branch 'dev' of rav.triplestart.com:jmudaka/stagfe2 into dev

= 3 gadi atpakaļ
vecāks
revīzija
bdf082716f

+ 32 - 0
app/Http/Controllers/StatTreeController.php

@@ -28,6 +28,10 @@ class StatTreeController extends Controller
     {
         return view('app.stat-tree.stat-trees.sub.dashboard', compact('statTree'));
     }
+    public function dashboard2(StatTree $statTree)
+    {
+        return view('app.stat-tree.stat-trees.sub.dashboard2', compact('statTree'));
+    }
 
     public function create(Request $request)
     {
@@ -66,6 +70,9 @@ class StatTreeController extends Controller
     }
 
     public function replaceAllLines(Request $request){
+
+        $parents = [];
+
         $statTreeID = (int) $request->get('statTreeID');
         $data = $request->get('data');
         $rows = json_decode($data, true);
@@ -89,6 +96,7 @@ class StatTreeController extends Controller
 
             DB::statement("DELETE FROM stat_tree_line_clause WHERE stat_tree_line_id = :stat_tree_line_id", ['stat_tree_line_id' => $statTreeLine->id]);
 
+            $allClauses = [];
             for($i = 0; $i < count($row); $i++){
                 $cell = $row[$i];
                 if(!$cell || empty($cell)) continue;
@@ -108,7 +116,31 @@ class StatTreeController extends Controller
                 $statTreeLineClause->position_index = $i;
                 $statTreeLineClause->detail_json = json_encode(['model' => $model]);
                 $statTreeLineClause->save();
+                $allClauses[] = $cell;
+            }
+            $parents[implode("|", $allClauses)] = $statTreeLine;
 
+            // if child, find and set parent
+            $nonEmpty = [];
+            for($i = 0; $i < count($row); $i++){
+                if($row[$i] && !empty($row[$i])) $nonEmpty[] = $row[$i];
+            }
+            if(count($nonEmpty) > 1) {
+                $parentClauses = [];
+                for($i = 0; $i < count($nonEmpty) - 1; $i++){
+                    $cell = $nonEmpty[$i];
+                    $clause = Clause::where('label', $cell)->where('model', 'ilike', $model)->first();
+                    if(!$clause){
+                        DB::rollBack();
+                        return $this->fail('No clause record found for ' . $cell);
+                    }
+                    $parentClauses[] = $clause->label;
+                }
+                $parentClauses = implode("|", $parentClauses);
+                if(@$parents[$parentClauses]) {
+                    $statTreeLine->parent_stat_tree_line_id = $parents[$parentClauses]->id;
+                    $statTreeLine->save();
+                }
             }
         }
         DB::commit();

+ 12 - 0
app/Models/StatTreeLine.php

@@ -16,4 +16,16 @@ class StatTreeLine extends Model
     public function statTree(){
         return $this->hasOne(StatTree::class, 'id', 'stat_tree_id');
     }
+    public function children(){
+        return $this->hasMany(StatTreeLine::class, 'parent_stat_tree_line_id', 'id');
+    }
+    public function displayLabel() {
+        $lastStatTreeLineClause = StatTreeLineClause::where('stat_tree_line_id', $this->id)
+            ->orderBy('position_index', 'DESC')
+            ->first();
+        if($lastStatTreeLineClause) {
+            return $lastStatTreeLineClause->clause_label;
+        }
+        return '-';
+    }
 }

+ 6 - 0
public/css/style.css

@@ -2730,4 +2730,10 @@ table.stag-compact-grid>tbody>tr>td [if-grid-view] {
     font-weight: bold;
     text-decoration: underline;
     margin-left: 10px;
+}
+.stat-tree-view {
+    padding: 0.5rem;
+}
+.stat-tree-view .stat-tree-node {
+    padding: 0.1rem 0.3rem;
 }

+ 1 - 1
resources/views/app/stat-tree/stat-trees/single.blade.php

@@ -20,7 +20,7 @@
                         </div>
                     </div>
                     <div>
-                        <a href="" class="btn btn-sm btn-primary text-white"><i class="fas fa-network-wired"></i> View All Stat Trees</a>
+                        <a href="{{route('practice-management.statTrees.list')}}" class="btn btn-sm btn-primary text-white"><i class="fas fa-network-wired"></i> View All Stat Trees</a>
                     </div>
                 </div>
             </div>

+ 2 - 2
resources/views/app/stat-tree/stat-trees/sub/dashboard.blade.php

@@ -87,8 +87,8 @@
                                 <table class="table table-bordered table-hover">
                                     <tbody>
                                         <tr v-for="(row, rowIndex) in rows">
-                                            <td v-for="(column, columnIndex) in columns[rowIndex]">
-                                                <input class="border-0" type="text" v-model="columns[rowIndex][columnIndex]" @keyup="updateColumns(rowIndex, columnIndex)">
+                                            <td v-for="(column, columnIndex) in columns[rowIndex]" class="p-0">
+                                                <input class="border-0 form-control p-1" type="text" v-model="columns[rowIndex][columnIndex]" @keyup="updateColumns(rowIndex, columnIndex)">
                                             </td>
                                         </tr>
                                     </tbody>

+ 234 - 0
resources/views/app/stat-tree/stat-trees/sub/dashboard2.blade.php

@@ -0,0 +1,234 @@
+@extends('app.stat-tree.stat-trees.single')
+@section('page')
+
+<div id="statTreeViewPage">
+    <div id="statTreeView" class="row">
+        @if(count($statTree->lines))
+            <div class="col-12">
+                <div class="d-flex align-items-center justify-content-between mb-2">
+                    <h6 class="font-weight-bold m-0">{{$statTree->name}}</h6>
+                    <div class="ml-auto">
+                        <button @click="refreshTreeCountQueries" class="btn btn-sm btn-danger text-white">
+                            <span v-if="!refreshing"><i class="fas fa-sync-alt"></i> Refresh Counts</span>
+                            <span v-else><i class="fas fa-circle-notch fa-spin"></i> Refreshing...</span>
+                        </button>
+                    </div>
+                </div>
+                @include('app.stat-tree.tree', ['slug' => 'rm-tree'])
+            </div>
+        @endif
+
+        <div class="col-12">
+            <h6 class="font-weight-bold bg-light p-3 mt-3">Replace Stat Tree Lines</h6>
+            <div class="row">
+                <div class="col-md-6">
+                    <div class="bg-white p-3">
+                        <h6 class="font-weight-bold">TSV</h6>
+                        <div class="input-group">
+                            <textarea v-model="content" class="form-control" rows="10" @keyup="splitContents"></textarea>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-6">
+                    <div class="bg-white p-3">
+                        <h6 class="font-weight-bold">JSON Structure</h6>
+                        <textarea class="form-control" rows="10">@{{jsonStructure}}</textarea>
+                    </div>
+                </div>
+            </div>
+            <template v-if="rows.length">
+                <div class="row my-3">
+                    <div class="col">
+                        <div class="bg-white p-3">
+                            <h6 class="font-weight-bold">Table output:</h6>
+                            <div class="table-responsive">
+                                <table class="table table-bordered table-hover">
+                                    <tbody>
+                                        <tr v-for="(row, rowIndex) in rows">
+                                            <td v-for="(column, columnIndex) in columns[rowIndex]" class="p-0">
+                                                <input class="border-0 form-control p-1" type="text" v-model="columns[rowIndex][columnIndex]" @keyup="updateColumns(rowIndex, columnIndex)">
+                                            </td>
+                                        </tr>
+                                    </tbody>
+                                </table>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </template>
+            <div class="row">
+                <div class="col-md-12">
+                    <div class="px-3">
+                        <button type="button" class="btn btn-sm btn-primary" @click="submit">Submit</button>
+                        <button type="button" class="btn btn-sm btn-secondary" @click="generateAutoFilledJson">Fill</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+    </div>
+</div>
+
+<script>
+    (function() {
+        function init() {
+            new Vue({
+                el: '#statTreeView',
+                data: {
+                    content: '',
+                    rowSplitter: '\n',
+                    columnSplitter: '\t',
+                    rows: [],
+                    columns: [],
+                    maxColumn: 0,
+                    jsonStructure: '',
+                    isAutoFilled: false,
+                    refreshing: false,
+                    refreshingLine: false
+                },
+                delimiters: ['@{{', '}}'],
+                methods: {
+                    updateColumns(rowIndex, columnIndex) {
+                        try {
+                            this.rows[rowIndex] = this.columns[rowIndex].join(this.columnSplitter);
+                            this.content = this.rows.join('\n');
+                        } catch (error) {
+                            console.error(error);
+                            this.rows = [];
+                            this.content = '';
+                        }
+                        this.updateJsonStructure();
+                    },
+                    splitContents() {
+                        this.isAutoFilled = false;
+                        this.maxColumn = 0;
+                        try {
+                            this.rows = this.content.split(this.rowSplitter);
+                            this.rows.map((row, index) => {
+                                this.columns[index] = row.split(this.columnSplitter);
+                                const length = this.columns[index].length;
+                                if (length > this.maxColumn) {
+                                    this.maxColumn = length
+                                }
+                                const emptyFields = this.maxColumn - length;
+                                for (ii = 0; ii < emptyFields; ii++) {
+                                    this.columns[index].push('');
+
+                                }
+                            });
+                        } catch (error) {
+                            console.error(error);
+                            this.rows = [];
+                        }
+                        this.updateJsonStructure();
+                    },
+                    updateJsonStructure() {
+                        try {
+                            this.jsonStructure = JSON.stringify(this.columns, null, '\t');
+                        } catch (error) {
+                            console.error(error);
+                            this.jsonStructure = JSON.stringify([], null, '\t');
+                        }
+                    },
+                    numberOfTabs: function (text) {
+                        var count = 0;
+                        var index = 0;
+                        while (text.charAt(index++) === "\t") {
+                            count++;
+                        }
+                        return count;
+                    },
+                    generateAutoFilledJson: function () {
+                        if(this.isAutoFilled) return;
+                        var string = tsvToArray.getAutoFilledTabContent(this.content);
+                        this.content = string;
+                        this.splitContents();
+                        this.isAutoFilled = true;
+                    },
+                    submit: function () {
+                        var data = JSON.stringify(this.columns);
+                        $.post('{{ route("practice-management.api.statTree.replaceAllLines") }}', {
+                            statTreeID: "{{ $statTree->id }}",
+                            data
+                        }, function (response) {
+                            if (response.success) {
+                                location.href = "{{ route('practice-management.statTrees.view.dashboard', $statTree) }}";
+                            } else {
+                                alert(response.message);
+                            }
+                        }, 'json');
+                    },
+                    refreshTreeCountQueries: function (evt) {
+                        evt.preventDefault();
+                        var self = this;
+                        if (self.refreshing) return;
+
+                        self.refreshing = true;
+                        $.post("{{ route('practice-management.api.statTree.refreshTreeCountQueries') }}", {
+                            statTreeID: "{{ $statTree->id }}"
+                        }, function (response) {
+                            self.refreshing = false;
+                            if (response.success) {
+                                fastLoad("{{ route('practice-management.statTrees.view.dashboard', $statTree) }}");
+                                //fastReload()
+
+                            } else {
+                                toastr.error(response.message);
+                            }
+                        }, 'json');
+                    },
+                    initRefreshLineQuery: function () {
+                        var self = this;
+
+                        $('[refresh-line-query]').click(function () {
+                            var element = $(this);
+                            var id = element.data('id');
+                            if (self.refreshingLine) return;
+                            self.refreshingLine = true;
+                            element.html('<i class="fas fa-circle-notch fa-spin"></i> Refreshing...');
+                            $.post("{{ route('practice-management.api.statTreeLine.refreshCountQuery') }}", {
+                                statTreeLineID: id
+                            }, function (response) {
+                                self.refreshingLine = false;
+                                if (response.success) {
+                                    $('#line-' + id).text(response.data);
+                                    toastr.success('Updated value: ' + response.data);
+                                } else {
+                                    toastr.error(reasponse.message);
+                                }
+                                element.html('Refresh');
+                            }, 'json');
+                        });
+                    },
+                    init: function () {
+                        this.initRefreshLineQuery();
+                    }
+                },
+                mounted: function () {
+                    this.init();
+                },
+                updated: function () {
+                    var self = this;
+                    self.$nextTick(function () {
+
+                    });
+                },
+                watch: {
+                    maxColumn(maxColumn) {
+                        this.columns.map((column, index) => {
+                            const emptyFields = maxColumn - column.length;
+                            if (emptyFields) {
+                                for (ii = 0; ii < emptyFields; ii++) {
+                                    this.columns[index].push('');
+                                }
+                            }
+                        })
+                    }
+                }
+            });
+        }
+        addMCInitializer('statTreeView', init, '#statTreeViewPage')
+    }).call(window);
+</script>
+
+@endsection

+ 28 - 0
resources/views/app/stat-tree/tree.blade.php

@@ -0,0 +1,28 @@
+<?php
+if (!function_exists('renderStatTreeLineNode')) {
+    function renderStatTreeLineNode($line)
+    {
+        echo '<div class="stat-tree-node">' .
+            $line->displayLabel() .
+            '&nbsp;&nbsp;<span class="text-secondary">(' . (is_null($line->last_refresh_count) ? '-' : $line->last_refresh_count) . ')</span>' .
+            '</div>';
+        if (count($line->children)) {
+            echo '<div class="pl-4">';
+            foreach ($line->children as $child) {
+                renderStatTreeLineNode($child);
+            }
+            echo '</div>';
+        }
+    }
+}
+$statTree = \App\Models\StatTree::where('slug', $slug)->first();
+?>
+@if(@$statTree)
+    <div class="stat-tree-view border mb-3">
+        @foreach($statTree->lines as $line)
+            @if(!$line->parent_stat_tree_line_id)
+                <?php renderStatTreeLineNode($line); ?>
+            @endif
+        @endforeach
+    </div>
+@endif

+ 2 - 1
routes/web.php

@@ -317,7 +317,8 @@ Route::middleware('pro.auth')->group(function () {
             Route::get('', 'StatTreeController@list')->name('list');
             Route::get('create', 'StatTreeController@createPage')->name('createPage');
             Route::name('view.')->prefix('view/{statTree}')->group(function () {
-                Route::get('', 'StatTreeController@dashboard')->name('dashboard');
+                Route::get('', 'StatTreeController@dashboard2')->name('dashboard');
+                Route::get('old', 'StatTreeController@dashboard')->name('dashboard2');
             });
         });
         Route::name('statTreeLines.')->prefix('stat-tree-lines/')->group(function () {

+ 4 - 4
spec/rm_clauses.txt

@@ -1,9 +1,9 @@
-care_month join client on care_month.client_id = client.id	Start date?	2021-12-01	(care_month.start_date = '2021-12-01')
+care_month join client on care_month.client_id = client.id	Start date?	2022-01-01	(care_month.start_date = '2022-01-01')
 care_month join client on care_month.client_id = client.id	Enrolled in RPM?	YES	(client.is_enrolled_in_rm = 'YES')
-care_month join client on care_month.client_id = client.id	Enrolled in RPM?	NO	(client.is_enrolled_in_rm = 'NO')
+care_month join client on care_month.client_id = client.id	Enrolled in RPM?	NO	(client.is_enrolled_in_rm = 'NO' OR client.is_enrolled_in_rm IS NULL OR client.is_enrolled_in_rm = 'UNKNOWN')
 care_month join client on care_month.client_id = client.id	Have cellular device?	YES	(SELECT COUNT(client_bdt_device.id) FROM client_bdt_device JOIN bdt_device bd on client_bdt_device.device_id = bd.id WHERE client_bdt_device.client_id = client.id) > 0		
-care_month join client on care_month.client_id = client.id	Have been seen w/in 90 days?	YES	(DATE_PART('day', client.most_recent_completed_mcp_note_date::timestamp - care_month.start_date::timestamp) <= 90)
-care_month join client on care_month.client_id = client.id	Have been seen w/in 90 days?	NO	(DATE_PART('day', client.most_recent_completed_mcp_note_date::timestamp - care_month.start_date::timestamp) > 90)
+care_month join client on care_month.client_id = client.id	Have been seen w/in 90 days?	YES	(DATE_PART('day', care_month.start_date::timestamp - client.most_recent_completed_mcp_note_date::timestamp) <= 90)
+care_month join client on care_month.client_id = client.id	Have been seen w/in 90 days?	NO	(client.most_recent_completed_mcp_note_date is null OR (DATE_PART('day', care_month.start_date::timestamp - client.most_recent_completed_mcp_note_date::timestamp) > 90))
 care_month join client on care_month.client_id = client.id	Have been spoken to this month?	YES	(care_month.has_anyone_interacted_with_client_about_rm_outside_note = TRUE)
 care_month join client on care_month.client_id = client.id	Have been spoken to this month?	NO	(care_month.has_anyone_interacted_with_client_about_rm_outside_note IS NULL OR care_month.has_anyone_interacted_with_client_about_rm_outside_note = FALSE)
 care_month join client on care_month.client_id = client.id	Have no unstamped meas.?	YES	(care_month.rm_num_measurements_not_stamped_by_mcp = 0)

+ 1 - 1
spec/rm_stat_tree_sample.txt

@@ -1,4 +1,4 @@
-Start date? 2021-12-01
+Start date? 2022-01-01
 	Enrolled in RPM? YES
 		Have cellular device? YES
 			Have been seen w/in 90 days? NO

+ 94 - 0
spec/rpt-queries.sql

@@ -0,0 +1,94 @@
+-- no longer active AND device-last-used within :xyz
+-- how long since last visit
+
+-- patients created on/after 2022-01-09 without MCP
+select count(id)
+from client where shadow_pro_id is null and mcp_pro_id is null and created_at::date >= '2022-01-09'
+
+
+select client.id, client.uid, name_first, name_last, cell_number, source, initiative, client_engagement_status_category,
+       cpc.commercial_payer_name,
+       (cpc.auto_medicare_detail_json::json)->'plan_details'->'MD'->'payer_name'
+from client left join client_primary_coverage cpc on client.latest_client_primary_coverage_id = cpc.id
+where shadow_pro_id is null and mcp_pro_id is null and client.created_at::date >= '2022-01-09'
+  --  and cpc.auto_medicare_is_match_found = true
+order by name_first;
+
+
+
+
+select client.created_at, client.id, client.uid, name_first, name_last, cell_number, source, initiative, client_engagement_status_category,
+       (select count(id) from note where note.is_cancelled is false and note.is_signed_by_hcp is true and note.client_id = client.id) as num_notes
+from client
+where
+        (select count(id) from note where note.is_cancelled is false and note.is_signed_by_hcp is true and note.client_id = client.id) > 0 AND
+    shadow_pro_id is null and client.created_at::date >= '2022-01-09' and (client.client_engagement_status_category <> 'DUMMY' OR client.client_engagement_status_category is null)
+  and client.is_part_b_primary = 'YES'
+order by created_at desc;
+
+
+
+select count(client.id),
+       (select pro.name_display from pro where pro.id = client.mcp_pro_id) as mcp,
+       client.mcp_pro_id, client.created_at::date
+from client
+where
+        (select count(id) from note where note.is_cancelled is false and note.is_signed_by_hcp is true and note.client_id = client.id) > 0 AND
+    shadow_pro_id is null and client.created_at::date >= '2022-01-16' and (client.client_engagement_status_category <> 'DUMMY' OR client.client_engagement_status_category is null)
+  -- and client.is_part_b_primary = 'YES'
+group by mcp_pro_id, client.created_at::date
+order by client.created_at::date desc, count desc
+
+
+select count(client.id),
+       (select pro.name_display from pro where pro.id = client.created_by_pro_id) as creator,
+       client.created_by_pro_id, client.created_at::date
+from client
+where
+        (select count(id) from note where note.is_cancelled is false and note.is_signed_by_hcp is true and note.client_id = client.id) > 0 AND
+    shadow_pro_id is null and client.created_at::date >= '2022-01-16' and (client.client_engagement_status_category <> 'DUMMY' OR client.client_engagement_status_category is null)
+  -- and client.is_part_b_primary = 'YES'
+group by created_by_pro_id, client.created_at::date
+order by client.created_at::date desc, count desc
+
+
+
+select client.created_at, client.id, client.uid, name_first, name_last, cell_number, source, initiative, client_engagement_status_category,
+       (select count(id) from note where note.is_cancelled is false and note.is_signed_by_hcp is true and note.client_id = client.id) as num_notes,
+       client.mailing_address_state
+from client
+where
+        (select count(id) from note where note.is_cancelled is false and note.is_signed_by_hcp is true and note.client_id = client.id) > 0 AND
+    shadow_pro_id is null and client.created_at::date >= '2022-01-09' and (client.client_engagement_status_category <> 'DUMMY' OR client.client_engagement_status_category is null)
+  and client.is_part_b_primary = 'YES' and mcp_pro_id = 1175
+order by created_at desc;
+
+
+
+SELECT
+    client.name_first, client.name_last, (select pro.name_display from pro where pro.id = client.mcp_pro_id) as mcp, client.most_recent_completed_mcp_note_date, client.client_engagement_status_category,
+    (SELECT COUNT(*) FROM measurement m WHERE m.client_id = client.id AND m.is_cellular_zero is false and m.ts_date_time::date >= '2021-11-01' AND ts_date_time::date <= '2021-11-30' and m.is_removed is false) as measurements
+FROM client
+WHERE
+        id IN (SELECT client_id FROM client_bdt_device) -- have devices
+  AND is_part_b_primary = 'YES'  -- are part b primary
+  -- have 16+ measurements in dec
+  AND (SELECT COUNT(*) FROM measurement m WHERE m.client_id = client.id AND m.is_cellular_zero is false and m.ts_date_time::date >= '2021-11-01' AND ts_date_time::date <= '2021-11-30' and m.is_removed is false) > 0
+  -- have not been see in over 60 days
+  AND client.most_recent_completed_mcp_note_date < (now() - interval '200 day')::date
+
+
+SELECT
+    count(client.id), (select pro.name_display from pro where pro.id = client.mcp_pro_id) as mcp
+FROM client
+WHERE
+        id IN (SELECT client_id FROM client_bdt_device) -- have devices
+  AND is_part_b_primary = 'YES'  -- are part b primary
+  -- have 16+ measurements in dec
+  AND (SELECT COUNT(distinct(m.created_at::date)) FROM measurement m WHERE m.client_id = client.id AND m.is_cellular_zero is false and m.created_at::date >= '2021-11-01' AND m.created_at::date <= '2021-11-30' and m.is_removed is false) > 0
+  -- have not been see in over 60 days
+  AND client.most_recent_completed_mcp_note_date < (now() - interval '100 day')::date
+group by mcp_pro_id
+
+
+-- DATE | MCP | COUNT