Index: branches/eam_branches/ipp-20230313/ippMonitor/INSTALL
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/INSTALL	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/INSTALL	(revision 42819)
@@ -39,5 +39,11 @@
 # $CZARPLOTDIR and $METRICSPLOTDIR will be generated as links in the same folder as ippMonitor  
 # the structure of the names for these two directories are defined in Makefile.in a
-psconfigure --htdocs /var/www/localhost/htdocs --wwwbin /var/www/localhost/wwwbin --pltdir /some/path --site ~/ippconfig/ippmonitor.config --usePDO
+
+# pltdir is for ippMetrics and czartool_plots hyperlinked from /export/ipp113.0/ipp/ in /var/www/localhost/htdocs/ippMonitor
+# pltdir in ipp117 is /var/www/localhost/htdocs/ippMonitor
+# ippMetrics -> /export/ipp113.0/ipp/ippMetrics/
+# czartool_plots -> /export/ipp113.0/ipp/czartool_plots/
+# --usePDO selects a modern PHP/mysql interface
+psconfigure --htdocs /var/www/localhost/htdocs --wwwbin /var/www/localhost/wwwbin --pltdir /var/www/localhost/htdocs/ippMonitor --site ~/ippconfig/ippmonitor.config --usePDO
 
 # note that modern php requires the use of PDO, the older MDB2 and DB mysql interfaces have been deprecated
Index: branches/eam_branches/ipp-20230313/ippMonitor/Makefile.in
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/Makefile.in	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/Makefile.in	(revision 42819)
@@ -91,4 +91,6 @@
 $(DESTWWW)/scatterPlotAirmassMcal.php \
 $(DESTWWW)/scatterPlotDataQuality.php \
+$(DESTWWW)/dataquality.php \
+$(DESTWWW)/pointing.php \
 $(DESTWWW)/loader.js
 
Index: branches/eam_branches/ipp-20230313/ippMonitor/czartool/czarpoll.pl
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/czartool/czarpoll.pl	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/czartool/czarpoll.pl	(revision 42819)
@@ -380,4 +380,9 @@
         }
 
+        # check ippdb host IPP-2338
+        print "* Checking ippdb hosts\n";
+        my @hosts = qw(ipphome0 ipphome1 ippdb01 ippdb05 ippdb06 ippdb09 ippdb11 ippdb12 ippdb13 ippdb14);
+        if($dbupdate == 1) {check_disk_usage_and_update($czarDb, @hosts);}
+
         # check pantasks status
         if($dbupdate == 1) {updateServerStatus();}
@@ -604,2 +609,48 @@
 }
 
+###########################################################################
+#
+# Loops through given host and update to the host list
+#
+###########################################################################
+sub check_disk_usage_and_update {
+    my ($czarDb, @hosts) = @_;
+
+    foreach my $host (@hosts) {
+        print "Checking $host...\n";
+
+        for my $suffix (0, 1) {
+            my $mount_point = "/export/$host.$suffix";
+
+            # Use SSH to get disk stats
+            my $df_output = `ssh $host df -k $mount_point 2>/dev/null`;
+
+            #if (!$df_output || $df_output !~ /\d/) {
+            #    warn "Failed to get disk usage for $host on $mount_point\n";
+            #    next;
+            #}
+
+            my @lines = split /\n/, $df_output;
+            my $line = $lines[1] || next;
+
+            my ($filesystem, $blocks, $used, $available, $percent, $mount) = split /\s+/, $line;
+
+            # Convert KB to TB
+            my $total_tb     = sprintf("%.1f", $blocks    / 1024 / 1024 / 1024);
+            my $used_tb      = sprintf("%.1f", $used      / 1024 / 1024 / 1024);
+            my $available_tb = sprintf("%.1f", $available / 1024 / 1024 / 1024);
+
+            my $used_percent      = sprintf("%.3f", $used / $blocks * 100);
+            my $available_percent = sprintf("%.3f", $available / $blocks * 100);
+
+            my $readable = 1;
+            my $writable = 1;
+
+            #print "Mount $mount_point: total=${total_tb}TB, used=${used_tb}TB (${used_percent}%), available=${available_tb}TB (${available_percent}%)\n";
+
+            # Example: ippdb01.0(3.5T)
+            my $host_label = "$host.$suffix(${total_tb}T)";
+            $czarDb->updateHost($host_label, 100, $available_percent, $used_percent, $readable, $writable);
+        }
+    }
+}
Index: branches/eam_branches/ipp-20230313/ippMonitor/czartool/czartool/Nebulous.pm
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/czartool/czartool/Nebulous.pm	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/czartool/czartool/Nebulous.pm	(revision 42819)
@@ -111,5 +111,5 @@
 
             next;
-        } elsif (($line =~ m/(ipp[0-9]+\.[0-9]{1})\s.+[0-9]+\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)%.*/i)||
+        } elsif (($line =~ m/(ipp[0-9]+(?:_bck)?\.[0-9]{1})\s.+[0-9]+\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)%.*/i)||
                ($line =~ m/(ippb[0-9]+\.[0-9]{1})\s.+[0-9]+\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)%.*/i)
 # these nodes are gone
Index: branches/eam_branches/ipp-20230313/ippMonitor/czartool/czartool/Pantasks.pm
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/czartool/czartool/Pantasks.pm	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/czartool/czartool/Pantasks.pm	(revision 42819)
@@ -10,5 +10,5 @@
         "cleanup", 
         "distribution", 
-        "pstamp", 
+        "ippitc:pstamp", 
         "registration", 
         "stack", 
@@ -22,4 +22,5 @@
         "ippqub:stdscience_ws",
         "ippqub2:stdscience_ws",
+        "ippsky:stdscience_ipp138",
         "publishing", 
         "replication", 
@@ -27,5 +28,5 @@
         );
 # current home dir of gpc1 and gpc2 
-my $ipphome1 = "/home/panstarrs/ippitc";
+my $ipphome1 = "/home/panstarrs/ippps1";
 my $ipphome2 = "/home/panstarrs/ippps2";
 
Index: branches/eam_branches/ipp-20230313/ippMonitor/czartool/extra_labels.dat
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/czartool/extra_labels.dat	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/czartool/extra_labels.dat	(revision 42819)
@@ -1,4 +1,6 @@
 ## TdB20190626: add extra labels for processing in this file. Syntax is as follows: [labelname] [telescope]
 ## Priorities for the labels must be added to the priorities mySQL table for the relevant telescope, if desired.
-PV3.Pole.Reprocess.20180510 gpc1
+## PV3.Pole.Reprocess.20180510 gpc1
 ps_ud_QUB gpc1
+## update.ipp138.fix gpc1
+## update.ipp138.fix gpc2
Index: branches/eam_branches/ipp-20230313/ippMonitor/raw/czartool_labels.php
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/raw/czartool_labels.php	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/raw/czartool_labels.php	(revision 42819)
@@ -87,5 +87,5 @@
         "distribution",
         "ippqub:stdscience_ws",
-        "pstamp",
+        "ippitc:pstamp",
         "registration",
 //        "stack",
@@ -1668,4 +1668,9 @@
     $qry = $db->query($sql);
     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
+
+    // Set environment variables
+    putenv("PATH=/usr/local/bin:/usr/bin:/bin:/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/bin");
+    putenv("PERL5LIB=/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/lib");
+
     # not sure why neb-host doesn't work on ipp113 CCL.20210216
     exec("neb-host |cut -b 17-30,86-", $hostnoteall);
Index: branches/eam_branches/ipp-20230313/ippMonitor/raw/dataquality.php
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/raw/dataquality.php	(revision 42819)
+++ branches/eam_branches/ipp-20230313/ippMonitor/raw/dataquality.php	(revision 42819)
@@ -0,0 +1,522 @@
+<?php
+include 'ipp.php';
+include 'site.php';
+
+$ID = checkID();
+
+// require an explicit project
+if (! $ID['proj']) { projectform ($ID); }
+
+// connect to czarDb 
+$czardb = dbconnect($CZARDBNAME); 
+$projectdb = dbconnect($ID['proj']);
+
+if ($ID['menu']) {$myMenu = $ID['menu'];}
+else {$myMenu = "ipp.czar.dat";}
+
+menu($myMenu, 'Czartool on '.$lastUpdateTime, 'ipp.css', $ID['link'], $ID['proj']);
+
+$pass = $ID['pass'];
+$proj = $ID['proj'];
+$menu = $ID['menu'];
+
+// Fetch data based on range selection
+$range = isset($_GET['range']) ? $_GET['range'] : 'yday';
+
+echo "<table border=\"0\">";
+echo "<tr>";
+echo "  <td style=width:1240>";
+echo "    <h1 align=\"middle\">Data Quality Report</h1>";
+echo "  <center>";
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=yday">Last night (default)</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=day">Today (MJD '.getMJD().')</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=week">Past 7 days</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=month">Past 30 days</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=year">Past 365 days</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=all">All </a>   ';
+echo "  </td>";
+echo "</tr>";
+echo "</table>";
+echo getDataQuality($projectdb, $range);
+
+menu_end();
+
+/////////////////////////////////////////////////////////////////////////////
+//                                                                         //
+//                               Functions                                 //
+//                                                                         //
+/////////////////////////////////////////////////////////////////////////////
+
+###########################################################################
+#
+# Get data quality
+#
+###########################################################################
+function getDataQuality($db, $range = 'yday') {
+    // Start timer
+    $start = microtime(true);
+
+    $mjdDay  = getMJD();
+    $mjdDay_yearback = $mjdDay - 366;
+    $mjdDay_monthback = $mjdDay - 32;
+    $min_exp_id = 0;
+
+    // Determine the reference exp_name based on the selected range
+    switch ($range) {
+        case 'year':
+            $expname = "o" . $mjdDay_yearback . "%";
+            break;
+        case 'month':
+            $expname = "o" . $mjdDay_monthback . "%";
+            break;
+        case 'week':
+            $expname = "o" . ($mjdDay - 7) . "%";
+            break;
+        case 'day':
+            $expname = "o" . $mjdDay . "%";
+            break;
+        default:
+            $expname = "o" . ($mjdDay - 1) . "%"; // Default: last night
+            break;
+    }
+
+    // Query to get the minimum exp_id
+    $expIdQuery = "SELECT MIN(exp_id) AS min_exp_id FROM rawExp WHERE rawExp.exp_name LIKE '$expname'";
+    $result = $db->query($expIdQuery);
+
+    if (DB::isError($result)) {
+        die("Database error: " . $result->getMessage());
+    }
+
+    $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+
+    if (!empty($row['min_exp_id'])) {
+        $min_exp_id = $row['min_exp_id'];
+    //    echo "Minimum exp_id: " . $min_exp_id;
+    } else {
+        echo "No results found.";
+    }
+
+    //echo "<br><br>";
+
+    // Determine the date constraint based on the range
+    switch ($range) {
+        case 'year':
+            $dateConstraint = "dateobs >= NOW() - INTERVAL 365 DAY AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'month':
+            $dateConstraint = "dateobs >= NOW() - INTERVAL 30 DAY AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'week':
+            $expnames = array();
+            for ($i = 0; $i < 7; $i++) {
+                $expnames[] = "rawExp.exp_name LIKE 'o" . ($mjdDay - $i) . "%'";
+            }
+            $dateConstraint = "(" . implode(" OR ", $expnames) . ") AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'yday':
+            $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'day':
+            $dateConstraint = "rawExp.exp_name LIKE 'o$mjdDay%' AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'all':
+            $dateConstraint = "rawExp.exp_id >= 0";
+            break;
+        default: // Default to last night
+            $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
+            break;
+    }
+
+    // Updated SQL query
+    $sql = "select * from (SELECT SUBTIME(dateobs, '10:00:00') AS time,
+                   round(( 367*YEAR(dateobs)- FLOOR((7 * (YEAR(dateobs) + FLOOR((MONTH(dateobs) + 9) / 12))) / 4) + FLOOR(275 * MONTH(dateobs) / 9)
+                   + DAY(dateobs) + 1721013.5 + (HOUR(dateobs) / 24.0) + (MINUTE(dateobs) / 1440.0) + (SECOND(dateobs) / 86400.0) - 2400000.5), 5) AS MJD,
+                   filter, comment, ra*180/pi() as RA, decl*180/pi() as DECL, exp_name, alt, az, fwhm_major, fwhm_minor, zpt_obs, zpt_stdev, rawExp.exp_id, camRun.label
+            FROM rawExp 
+            JOIN chipRun USING (exp_id) 
+            JOIN camRun USING (chip_id) 
+            JOIN camProcessedExp USING (cam_id)
+            WHERE $dateConstraint
+              AND exp_type = 'OBJECT'
+              AND camRun.state LIKE 'full'
+              AND camProcessedExp.fault = 0
+              AND camProcessedExp.zpt_obs != 0 ORDER BY RAND() limit 5000) as sub order by time;";
+
+    $DQarray = array();
+    $qry = $db->query($sql);
+    if (dberror($qry)) {
+        echo "<b>Error with $sql</b><br>\n";
+        return;
+    }
+    //    echo "$sql<br><br>\n";
+
+    while ($qry->fetchInto($row)) {
+        $DQarray[] = $row;
+    }
+
+    // Ensure $DQarray is not empty
+    if (empty($DQarray)) {
+        echo "<center><b> No data for MJD: $mjdDay </b></center>";
+        return; // Stop execution if no data to encode
+    }
+
+    // Convert data array to JSON for easier handling in JavaScript
+    $dataJson = json_encode($DQarray);
+
+    if ($dataJson === false) {
+        echo "<b>Error encoding JSON:</b> " . json_last_error_msg(); // Display any JSON encoding errors
+        return; // Stop execution if JSON encoding fails
+    }
+
+    echo "<script>console.log(" . json_encode($dataJson) . ");</script>"; // Log JSON output to console for debugging
+echo <<<HTML
+<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js" onerror="loadLocalGoogleCharts()"></script>
+
+<script type="text/javascript">
+    // Check if Google Charts library loaded successfully
+    window.addEventListener("load", function() {
+        if (typeof google === "undefined" || typeof google.charts === "undefined") {
+            loadLocalGoogleCharts();
+        }
+    });
+
+    // Function to load local version of Google Charts if CDN fails
+    function loadLocalGoogleCharts() {
+        console.warn("Google Charts CDN failed, loading local loader.js instead.");
+        var script = document.createElement("script");
+        script.src = "loader.js"; // Path to your local loader.js file
+        document.head.appendChild(script);
+    }
+</script>
+
+<script type="text/javascript">
+    google.charts.load('current', {packages: ['corechart']});
+
+    // Use the encoded JSON directly in JavaScript
+    const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
+
+    // draw FWHM vs MJD
+    function drawFWHMChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'FWHM_major');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'FWHM_minor');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'seeing ~ 1"');
+            data.addColumn('number', 'seeing ~ 3"');
+
+        const rows = dataArray.map(row => {
+            const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id, cam_label] = row;
+
+            return [
+                parseFloat(mjd),      // Position Angle
+                parseFloat(iq_fwhm_maj),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+                parseFloat(iq_fwhm_min),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+                4,
+                12
+            ];
+        });
+
+        data.addRows(rows);
+
+        // Extract the first MJD value from dataArray
+        //const firstMJD = dataArray.length > 0 ? parseFloat(dataArray[0][1]) : "N/A"; 
+        const firstMJD = dataArray.length > 0 ? Math.floor(parseFloat(dataArray[0][1])) : "N/A";
+        console.log("First MJD Value:", firstMJD);
+
+        const options = {
+            title: 'FWHM (MJD: '+firstMJD+')',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 2, lineWidth: 0. },
+                1: { color: '#33a532', pointSize: 2, lineWidth: 0. },
+                2: { color: 'red', lineWidth: 2, pointSize: 0 },
+                3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'FWHM (pixels)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '8%', top: 60, right: '5%', bottom: 50}, // Adjust left and right
+            fontSize: 12,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_fwhm'));
+        chart.draw(data, options);
+    }
+
+    // draw zoomed zerpoint vs mjd
+    function drawZOOMZPTChart() {
+        var data = new google.visualization.DataTable();
+        data.addColumn('number', 'MJD');
+
+        // Add columns for each filter type to handle separate colors
+        data.addColumn('number', 'g (24.563, 25.013)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'r (24.750, 24.840)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'i (24.611, 24.711)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'z (24.240, 24.300)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'y (23.320, 23.570)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'w (26.000, 26.300)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id, cam_label] = row;
+
+            // Initialize all filter values as null
+            let zp_g = null, zp_r = null, zp_i = null, zp_z = null, zp_y = null, zp_w = null;
+
+            // Set the zeropoint offset based on filter and assign it to the correct series
+            switch (filter[0]) {
+                case 'g':
+                    zp_g = zpt_obs - 24.563;
+                    break;
+                case 'r':
+                    zp_r = zpt_obs - 24.750;
+                    break;
+                case 'i':
+                    zp_i = zpt_obs - 24.611;
+                    break;
+                case 'z':
+                    zp_z = zpt_obs - 24.240;
+                    break;
+                case 'y':
+                   zp_y = zpt_obs - 23.320;
+                    break;
+                case 'w':
+                    zp_w = zpt_obs - 26.000;
+                    break;
+            }
+
+            return [
+                parseFloat(mjd), // MJD value
+                zp_g, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_g - zpt_stdev), parseFloat(zp_g - zpt_stdev*-1),
+                zp_r, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_r - zpt_stdev), parseFloat(zp_r - zpt_stdev*-1),
+                zp_i, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_i - zpt_stdev), parseFloat(zp_i - zpt_stdev*-1),
+                zp_z, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_z - zpt_stdev), parseFloat(zp_z - zpt_stdev*-1),
+                zp_y, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_y - zpt_stdev), parseFloat(zp_y - zpt_stdev*-1),
+                zp_w, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_w - zpt_stdev), parseFloat(zp_w - zpt_stdev*-1)
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Zeropoint zoom-in (MJD)',
+            titleTextStyle: { color: 'black', fontSize: 15 },
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'start', maxLines: 3 },
+            series: {
+                0: { color: '#00f000', pointSize: 5, pointShape: 'circle',   linewidth: .0},  // 'g' filter
+                1: { color: '#f00000', pointSize: 5, pointShape: 'triangle', linewidth: .0},  // 'r' filter
+                2: { color: '#f05000', pointSize: 5, pointShape: 'square',   linewidth: .0},  // 'i' filter
+                3: { color: '#0050a0', pointSize: 5, pointShape: 'diamond',  linewidth: .0},  // 'z' filter
+                4: { color: '#ffe000', pointSize: 5, pointShape: 'star',     linewidth: .0},  // 'y' filter
+                5: { color: '#808080', pointSize: 5, pointShape: 'polygon',  linewidth: .0},  // 'w' filter
+            },
+            intervals: { color: 'red', lineWidth: .0, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: { count: -1 }
+            },
+            vAxis: {
+                title: 'Zeropoint (mag)',
+                viewWindow: {   min: -1.00, max: .5    },
+            },
+            tooltip: { isHtml: true },
+            chartArea: { left: '9%', top: 60, right: '5%', bottom: 50 },
+            fontSize: 12,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_zoomzpt'));
+        chart.draw(data, options);
+    }
+
+    // draw zeropoint vs mjd
+    function drawZPTChart() {
+        var data = new google.visualization.DataTable();
+        data.addColumn('number', 'MJD');
+
+        // Add columns for each filter type to handle separate colors
+        data.addColumn('number', 'g' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'r' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'i' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'z' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'y' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'w' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+
+        // Count occurrences of each filter
+        const filterCounts = { g: 0, r: 0, i: 0, z: 0, y: 0, w: 0 };
+       const rows = dataArray.map(row => {
+            const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id, cam_label] = row;
+
+            // Initialize all filter values as null
+            let zp_g = null, zp_r = null, zp_i = null, zp_z = null, zp_y = null, zp_w = null;
+
+            // Increment the count for the filter
+            if (filterCounts.hasOwnProperty(filter[0])) {
+                filterCounts[filter[0]] += 1;
+            }
+
+            // Set the zeropoint offset based on filter and assign it to the correct series
+            switch (filter[0]) {
+                case 'g':
+                    zp_g = zpt_obs - 0.0;
+                    break;
+                case 'r':
+                    zp_r = zpt_obs - 0.0;
+                    break;
+                case 'i':
+                    zp_i = zpt_obs - 0.0;
+                    break;
+                case 'z':
+                    zp_z = zpt_obs - 0.0;
+                    break;
+                case 'y':
+                    zp_y = zpt_obs - 0.0;
+                    break;
+                case 'w':
+                    zp_w = zpt_obs - 0.0;
+                    break;
+            }
+
+            return [
+                parseFloat(mjd), // MJD value
+                zp_g, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_g - zpt_stdev), parseFloat(zp_g - zpt_stdev*-1),
+                zp_r, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_r - zpt_stdev), parseFloat(zp_r - zpt_stdev*-1),
+                zp_i, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_i - zpt_stdev), parseFloat(zp_i - zpt_stdev*-1),
+                zp_z, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_z - zpt_stdev), parseFloat(zp_z - zpt_stdev*-1),
+                zp_y, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_y - zpt_stdev), parseFloat(zp_y - zpt_stdev*-1),
+                zp_w, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,  
+parseFloat(zp_w - zpt_stdev), parseFloat(zp_w - zpt_stdev*-1),
+            ];
+        });
+      data.addRows(rows);
+
+        // Include counts in the title using string concatenation
+        const countsTitle = 'Zeropoint (MJD) g: ' + filterCounts.g + ', r: ' + filterCounts.r + ', i: ' + filterCounts.i + ', z: ' + filterCounts.z + ', y: ' + filterCounts.y + ', w: ' + filterCounts.w;
+
+
+        console.log(countsTitle);
+
+        const options = {
+            title: countsTitle,
+            titleTextStyle: { color: 'black', fontSize: 15 },
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#00f000', pointSize: 5, pointShape: 'circle',   linewidth: 1},  // 'g' filter
+                1: { color: '#f00000', pointSize: 5, pointShape: 'triangle', linewidth: 1},  // 'r' filter
+                2: { color: '#f05000', pointSize: 5, pointShape: 'square',   linewidth: 1},  // 'i' filter
+                3: { color: '#0050a0', pointSize: 5, pointShape: 'diamond',  linewidth: 1},  // 'z' filter
+                4: { color: '#ffe000', pointSize: 5, pointShape: 'star',     linewidth: 1},  // 'y' filter
+                5: { color: '#808080', pointSize: 5, pointShape: 'polygon',  linewidth: 1},  // 'w' filter
+            },
+            intervals: { color: 'red', lineWidth: 0, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: { count: -1 }
+            },
+            vAxis: {
+                title: 'Zeropoint (mag)'
+            },
+            tooltip: { isHtml: true },
+            chartArea: { left: '9%', top: 60, right: '5%', bottom: 50 },
+            fontSize: 12,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_zpt'));
+        chart.draw(data, options);
+    }
+
+    google.charts.setOnLoadCallback(drawFWHMChart);
+    google.charts.setOnLoadCallback(drawZPTChart);
+    google.charts.setOnLoadCallback(drawZOOMZPTChart);
+
+</script>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_fwhm" style="width:100%;"></div>
+    <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_zpt" style="width:100%;"></div>
+    <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_zoomzpt" style="width:100%;"></div>
+    <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
+</div>
+
+HTML;
+    // End timer
+    $total_time = round(microtime(true) - $start, 3);
+    echo "<center>loading plot in $total_time seconds<br></center>";
+
+}
+
+
+
+?>
+
Index: branches/eam_branches/ipp-20230313/ippMonitor/raw/ipp.czar.dat
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/raw/ipp.czar.dat	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/raw/ipp.czar.dat	(revision 42819)
@@ -5,4 +5,6 @@
 menulink   | menuselect      | link    | czartool               | czartool_labels.php
 menulink   | menuselect      | link    | storage status         | storage.php
+menulink   | menuselect      | link    | pointing               | pointing.php
+menulink   | menuselect      | link    | data quality           | dataquality.php
 menulink   | menuselect      | link    | mask stats             | maskStats.php
 menulink   | menuselect      | link    | night summary          | nightSummary.php
Index: branches/eam_branches/ipp-20230313/ippMonitor/raw/ipp.php
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/raw/ipp.php	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/raw/ipp.php	(revision 42819)
@@ -982,5 +982,3 @@
 }
 
-
-
 ?>
Index: branches/eam_branches/ipp-20230313/ippMonitor/raw/pointing.php
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/raw/pointing.php	(revision 42819)
+++ branches/eam_branches/ipp-20230313/ippMonitor/raw/pointing.php	(revision 42819)
@@ -0,0 +1,505 @@
+<?php
+include 'ipp.php';
+include 'site.php';
+
+$ID = checkID();
+
+// require an explicit project
+if (! $ID['proj']) { projectform ($ID); }
+
+// connect to czarDb 
+$czardb = dbconnect($CZARDBNAME); 
+$projectdb = dbconnect($ID['proj']);
+
+if ($ID['menu']) {$myMenu = $ID['menu'];}
+else {$myMenu = "ipp.czar.dat";}
+
+menu($myMenu, 'Czartool on '.$lastUpdateTime, 'ipp.css', $ID['link'], $ID['proj']);
+
+$pass = $ID['pass'];
+$proj = $ID['proj'];
+$menu = $ID['menu'];
+
+// Fetch data based on range selection
+$range = isset($_GET['range']) ? $_GET['range'] : 'yday';
+
+echo "<table border=\"0\">";
+echo "<tr>";
+echo "  <td style=width:1240>";
+echo "    <h1 align=\"middle\">Pointing Report</h1>";
+echo "  <center>";
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=yday">Last night (default)</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=day">Today (MJD '.getMJD().')</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=week">Past 7 days</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=month">Past 30 days</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=year">Past 365 days</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=all">All </a>   ';
+echo "  </td>";
+echo "</tr>";
+echo "</table>";
+echo getPointing($projectdb, $range);
+
+menu_end();
+
+
+/////////////////////////////////////////////////////////////////////////////
+//                                                                         //
+//                               Functions                                 //
+//                                                                         //
+/////////////////////////////////////////////////////////////////////////////
+###########################################################################
+#
+# Get data quality
+#
+###########################################################################
+function getPointing($db, $range = 'yday') {
+    // Start timer
+    $start = microtime(true);
+
+    $mjdDay  = getMJD();
+    $mjdDay_yearback = $mjdDay - 366;
+    $mjdDay_monthback = $mjdDay - 32;
+    $min_exp_id = 0;
+
+    // Determine the reference exp_name based on the selected range
+    switch ($range) {
+        case 'year':
+            $expname = "o" . $mjdDay_yearback . "%";
+            break;
+        case 'month':
+            $expname = "o" . $mjdDay_monthback . "%";
+            break;
+        case 'week':
+            $expname = "o" . ($mjdDay - 7) . "%";
+            break;
+        case 'day':
+            $expname = "o" . $mjdDay . "%";
+            break;
+        default:
+            $expname = "o" . ($mjdDay - 1) . "%"; // Default: last night
+            break;
+    }
+
+    // Query to get the minimum exp_id
+    $expIdQuery = "SELECT MIN(exp_id) AS min_exp_id FROM rawExp WHERE rawExp.exp_name LIKE '$expname'";
+    $result = $db->query($expIdQuery);
+
+    if (DB::isError($result)) {
+        die("Database error: " . $result->getMessage());
+    }
+
+    $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+
+    if (!empty($row['min_exp_id'])) {
+        $min_exp_id = $row['min_exp_id'];
+    //    echo "Minimum exp_id: " . $min_exp_id;
+    } else {
+        echo "No results found.";
+    }
+
+    //echo "<br><br>";
+
+    // Determine the date constraint based on the range
+    switch ($range) {
+        case 'year':
+            $dateConstraint = "dateobs >= NOW() - INTERVAL 365 DAY AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'month':
+            $dateConstraint = "dateobs >= NOW() - INTERVAL 30 DAY AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'week':
+            $expnames = array();
+            for ($i = 0; $i < 7; $i++) {
+                $expnames[] = "rawExp.exp_name LIKE 'o" . ($mjdDay - $i) . "%'";
+            }
+            $dateConstraint = "(" . implode(" OR ", $expnames) . ") AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'yday':
+            $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'day':
+            $dateConstraint = "rawExp.exp_name LIKE 'o$mjdDay%' AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'all':
+            $dateConstraint = "rawExp.exp_id >= 0";
+            break;
+        default: // Default to last night
+            $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
+            break;
+    }
+ 
+    // Updated SQL query
+    $sql = "select * from (SELECT SUBTIME(dateobs, '10:00:00') AS time, AST_R0, AST_D0,
+                   round(( 367*YEAR(dateobs)- FLOOR((7 * (YEAR(dateobs) + FLOOR((MONTH(dateobs) + 9) / 12))) / 4) + FLOOR(275 * MONTH(dateobs) / 9)
+                   + DAY(dateobs) + 1721013.5 + (HOUR(dateobs) / 24.0) + (MINUTE(dateobs) / 1440.0) + (SECOND(dateobs) / 86400.0) - 2400000.5), 5) AS MJD,
+                   filter, exp_time, comment, ra*180/pi() as RA, decl*180/pi() as DECL, AST_RS, AST_DS, AST_T0, posang, exp_name, alt, az, rawExp.exp_id, camRun.label
+            FROM rawExp
+            JOIN chipRun USING (exp_id)
+            JOIN camRun USING (chip_id)
+            JOIN camProcessedExp USING (cam_id)
+            WHERE $dateConstraint
+              AND exp_type = 'OBJECT'
+              AND camRun.state LIKE 'full'
+              AND camProcessedExp.fault = 0
+              AND camProcessedExp.zpt_obs != 0 ORDER BY RAND() limit 5000) as sub order by time;";
+
+    $DQarray = array();
+    $qry = $db->query($sql);
+    if (dberror($qry)) {
+        echo "<b>Error with $sql</b><br>\n";
+        return;
+    }
+    //    echo "$sql<br><br>\n";
+    
+    while ($qry->fetchInto($row)) {
+        $DQarray[] = $row;
+    }
+    
+    // Ensure $DQarray is not empty
+    if (empty($DQarray)) {
+        echo "<center><b> No data for MJD: $mjdDay </b></center>";
+        return; // Stop execution if no data to encode
+    }
+    
+    // Convert data array to JSON for easier handling in JavaScript
+    $dataJson = json_encode($DQarray);
+    
+    if ($dataJson === false) {
+        echo "<b>Error encoding JSON:</b> " . json_last_error_msg(); // Display any JSON encoding errors
+        return; // Stop execution if JSON encoding fails
+    }
+    
+    echo "<script>console.log(" . json_encode($dataJson) . ");</script>"; // Log JSON output to console for debugging
+echo <<<HTML
+<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js" onerror="loadLocalGoogleCharts()"></script>
+
+<script type="text/javascript">
+    // Check if Google Charts library loaded successfully
+    window.addEventListener("load", function() {
+        if (typeof google === "undefined" || typeof google.charts === "undefined") {
+            loadLocalGoogleCharts();
+        }
+    });
+
+    // Function to load local version of Google Charts if CDN fails
+    function loadLocalGoogleCharts() {
+        console.warn("Google Charts CDN failed, loading local loader.js instead.");
+        var script = document.createElement("script");
+        script.src = "loader.js"; // Path to your local loader.js file
+        document.head.appendChild(script);
+    }
+</script>
+
+<script type="text/javascript">
+    google.charts.load('current', {packages: ['corechart']});
+
+    // Use the encoded JSON directly in JavaScript
+    const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
+
+    // draw pointing vs mjd
+    function drawMJDChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', '20 TP unit ~5.1"');
+            data.addColumn('number', '-20 TP unit ~5.1"');
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
+
+            return [
+                parseFloat(mjd),               // MJD
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+                20,                            // Dummy value for 20 TP unit
+                -20                            // Dummy value for -20 TP unit
+            ];
+        });
+
+        data.addRows(rows);
+
+        // Extract the first MJD value from dataArray
+        //const firstMJD = dataArray.length > 0 ? parseFloat(dataArray[0][1]) : "N/A";
+        const firstMJD = dataArray.length > 0 ? Math.floor(parseFloat(dataArray[0][3])) : "N/A";
+        console.log("First MJD Value:", firstMJD);
+
+        const options = {
+            title: 'Pointing Offset (MJD: '+firstMJD+')',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+                2: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }, // Line at y = 20
+                3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }  // Line at y = -20
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_mjd'));
+        chart.draw(data, options);
+    }
+
+    // draw pointing scatter
+    function drawSIGChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset_scatter (AST_RS)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset_scatter (AST_DS)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
+
+            return [
+                parseFloat(mjd),               // MJD
+                parseFloat(ra_offset_sig),     // RA_offset_scatter (AST_RS)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+                parseFloat(dec_offset_sig),    // Dec_offset_scatter (AST_DS)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset Scatter (MJD)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+                2: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }, // Line at y = 20
+                3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }  // Line at y = -20
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset Scatter (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_sig'));
+        chart.draw(data, options);
+    }
+
+
+    // position angle
+    function drawPOSChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
+
+            return [
+                parseFloat(position_ang),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Position Angle)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            // pointSize: 5, // Point size for scatter plot
+            // lineWidth: 2,
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Position Angle',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_pos'));
+        chart.draw(data, options);
+    }
+
+    function drawALTChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'Altitute');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
+
+            return [
+                parseFloat(alt),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Altitude)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Altitude',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            //chartArea: {left: 80, top: 50, right: 20, bottom: 50},
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_alt'));
+        chart.draw(data, options);
+    }
+
+   function drawAZChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'Azumith');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
+
+            return [
+                parseFloat(az),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Azumith)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Azumith',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            //chartArea: {left: 80, top: 50, right: 20, bottom: 50},
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_az'));
+        chart.draw(data, options);
+    }
+
+    google.charts.setOnLoadCallback(drawMJDChart);
+    google.charts.setOnLoadCallback(drawSIGChart);
+    google.charts.setOnLoadCallback(drawPOSChart);
+    google.charts.setOnLoadCallback(drawALTChart);
+    google.charts.setOnLoadCallback(drawAZChart);
+</script>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_mjd" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_sig" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_pos" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_alt" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_az" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+HTML;
+
+// End timer
+$total_time = round(microtime(true) - $start, 3);
+echo "<center>Loading plot in $total_time seconds<br></center>";
+
+}
+
+?>
+
Index: branches/eam_branches/ipp-20230313/ippMonitor/scripts/czartool_getServerStatus.pl
===================================================================
--- branches/eam_branches/ipp-20230313/ippMonitor/scripts/czartool_getServerStatus.pl	(revision 42751)
+++ branches/eam_branches/ipp-20230313/ippMonitor/scripts/czartool_getServerStatus.pl	(revision 42819)
@@ -19,7 +19,14 @@
 
 if ($proj eq 'gpc1') {
-  $user = 'ippitc';
+  $user = 'ippps1';
 } else {
   $user = 'ippps2';
+}
+
+my @cmdOut3 = `grep PANTASKS_SERVER ~$user/$server/ptolemy.rc |grep -v std | grep -v PORT 2>&1`;
+my $line3;
+foreach $line3 (@cmdOut3) {
+    chomp($line3);
+    print "$line3 \n";
 }
 
@@ -40,4 +47,5 @@
     my @fields = split /\s+/, $line2;
 
+
 #    run job example 
 # field0      1        2       3         4      5       6           7       8 
@@ -50,2 +58,4 @@
     $nline = $nline + 1;
 }
+
+
