text:
""" Handlers for sequencing project information.
"""
import json
import traceback

import tornado.web
import dateutil.parser
import datetime
import requests
import re
import base64
import os
import logging
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import markdown
import slack
import nest_asyncio
import itertools

from collections import defaultdict
from collections import OrderedDict
from status.util import dthandler, SafeHandler
from dateutil.relativedelta import relativedelta

from genologics import lims
from genologics.entities import Project
from genologics.entities import Sample
from genologics.entities import Process
from genologics.entities import Artifact
from genologics.entities import Protocol
from genologics.config import BASEURI, USERNAME, PASSWORD

from zenpy import Zenpy, ZenpyException

lims = lims.Lims(BASEURI, USERNAME, PASSWORD)
application_log=logging.getLogger("tornado.application")

class PresetsHandler(SafeHandler):
    """Handler to GET and POST/PUT personalized and default set of presets in

    project view.
    """
    def get(self):
        """Get preset choices of columns from StatusDB

        It will return a JSON with two lists of presets, the default ones and the user defined
        presets.
        """
        presets_list = self.get_argument('presets_list', 'pv_presets')
        self.set_header("Content-type", "application/json")
        user_details=self.get_user_details(self.application, self.get_current_user().email)
        presets = {
            "default": self.application.genstat_defaults.get(presets_list),
            "user": user_details.get('userpreset')
        }
        self.write(json.dumps(presets))

    def post(self):
        """Save/Delete preset choices of columns into StatusDB
        """
        doc=self.get_user_details(self.application, self.get_current_user().email)
        if self.get_arguments('save'):
            preset_name = self.get_argument('save')
            data = json.loads(self.request.body)
            if 'userpreset' in doc:
                doc['userpreset'][preset_name]=data
            else:
                doc['userpreset']={ preset_name:data }

        if self.get_arguments('delete'):
            preset_name = self.get_argument('delete')
            del doc['userpreset'][preset_name]

        try:
            self.application.gs_users_db.save(doc)
        except Exception as e:
            self.set_status(400)
            self.write(e.message)

        self.set_status(201)
        self.write({'success': 'success!!'})

    @staticmethod
    def get_user_details(app, user_email):
        if user_email == 'Testing User!':
            user_email=app.settings.get("username", None)+'@scilifelab.se'
        user_details={}
        headers = {"Accept": "application/json",
                   "Authorization": "Basic " + "{}:{}".format(base64.b64encode(bytes(app.settings.get("username", None), 'ascii')),
                   base64.b64encode(bytes(app.settings.get("password", None), 'ascii')))}
        for row in app.gs_users_db.view('authorized/users'):
            if row.get('key') == user_email:
                user_url = "{}/gs_users/{}".format(app.settings.get("couch_server"), row.get('value'))
                r = requests.get(user_url, headers=headers).content.rstrip()
                user_details=json.loads(r);
        return user_details

class PresetsOnLoadHandler(PresetsHandler):
    """Handler to GET and POST/PUT personalized and default set of presets on loading

    project view.
    """
    def get(self):
        action = self.get_argument('action', '')
        self.set_header("Content-type", "application/json")
        self.write(json.dumps(self.get_user_details(self.application, self.get_current_user().email).get('onload')))

    def post(self):
        doc=self.get_user_details(self.application, self.get_current_user().email)
        data = json.loads(self.request.body)
        action=self.get_argument('action')
        doc['onload']=data

        try:
            self.application.gs_users_db.save(doc)
        except Exception as e:
            self.set_status(400)
            self.write(e.message)

        self.set_status(201)
        self.write({'success': 'success!!'})

class ProjectsBaseDataHandler(SafeHandler):

    cached_search_list = None
    search_list_last_fetched = None

    def keys_to_names(self, columns):
        d = {}
        for column_category, column_tuples in columns.items():
            for key, value in column_tuples.items():
                d[value] = key
        return d

    def project_summary_data(self, row):
        # the details key gives values containing multiple udfs on project level
        # and project_summary key gives 'temporary' udfs that will move to 'details'.
        # Include these as normal key:value pairs
        if 'project_summary' in row.value:
            for summary_key, summary_value in row.value['project_summary'].items():
                row.value[summary_key] = summary_value
            row.value.pop("project_summary", None)

        # If key is in both project_summary and details, details has precedence
        if 'details' in row.value:
            for detail_key, detail_value in row.value['details'].items():
                row.value[detail_key] = detail_value
            row.value.pop("details", None)

        # Handle the pending reviews:
        if 'pending_reviews' in row.value:
            links = ', '.join(['{2} requested review from {3}'.format(BASEURI, rid[0], rid[1], rid[2]) for rid in row.value['pending_reviews']])
            row.value['pending_reviews'] = links

        # Find the latest running note, return it as a separate field
        if 'running_notes' in row.value:
            try:
                notes = json.loads(row.value['running_notes'])
                # note_dates = {datetime obj: time string, ...}
                note_dates = dict(zip(map(dateutil.parser.parse, notes.keys()), notes.keys()))
                latest_date = note_dates[max(list(note_dates))]
                row.value['latest_running_note'] = json.dumps({latest_date: notes[latest_date]})
            except ValueError:
                pass

        if row.key[0] in ['review', 'ongoing', 'reception control'] and 'queued' in row.value:
            #Add days ongoing in production field
            now = datetime.datetime.now()
            queued = row.value['queued']
            diff = now - dateutil.parser.parse(queued)
            row.value['days_in_production'] = diff.days
        elif row.key[0] in ['aborted', 'closed'] and 'close_date' and 'queued' in row.value:
            #Days project was in production
            close = dateutil.parser.parse(row.value['close_date'])
            diff = close - dateutil.parser.parse(row.value['queued'])
            row.value['days_in_production'] = diff.days
        if row.key[0] in ['review', 'ongoing', 'reception control'] and 'open_date' in row.value:
            end_date = datetime.datetime.now()
            if 'queued' in row.value:
                end_date =  dateutil.parser.parse(row.value['queued'])
            diff = (end_date - dateutil.parser.parse(row.value['open_date'])).days
            if 'queued' not in row.value and diff > 14:
                row.value['days_in_reception_control'] = '{}'.format(diff)
            else:
                row.value['days_in_reception_control'] = diff

        if 'order_details' in row.value and 'fields' in row.value['order_details'] and 'project_pi_name' in row.value['order_details']['fields']:
            row.value['project_pi_name'] = row.value['order_details']['fields']['project_pi_name']

        return row

    def _get_two_year_from(self, from_date):
        return (datetime.datetime.strptime(from_date, "%Y-%m-%d") - relativedelta(years=2)).strftime("%Y-%m-%d")

    def _calculate_days_in_status(self, start_date, end_date):
        days = 0
        if start_date:
            if end_date:
                delta = dateutil.parser.parse(end_date) - dateutil.parser.parse(start_date)
            else:
                delta = datetime.datetime.now() - dateutil.parser.parse(start_date)
            days = delta.days
        else:
            days = '-'
        return days

    def list_projects(self, filter_projects='all'):
        projects = OrderedDict()
        closedflag=False
        queuedflag=False
        openflag=False
        projtype=self.get_argument('type', 'all')

        def_dates_gen = { 'days_recep_ctrl' : ['open_date', 'queued'],
                          'days_analysis' : ['all_samples_sequenced', 'best_practice_analysis_completed'],
                          'days_data_delivery' : ['best_practice_analysis_completed', 'all_raw_data_delivered'],
                          'days_close' : ['all_raw_data_delivered', 'close_date']
                         }

        def_dates_summary = { 'days_prep_start' : ['queued', 'library_prep_start'],
                              'days_seq_start' : [['qc_library_finished', 'queued'], 'sequencing_start_date'],
                              'days_seq' : ['sequencing_start_date', 'all_samples_sequenced'],
                              'days_prep' : ['library_prep_start', 'qc_library_finished']
                             }

        if 'closed' in filter_projects or 'all' in filter_projects:
            closedflag=True
        if 'ongoing' in filter_projects or 'open' in filter_projects or 'review' in filter_projects or 'all' in filter_projects or 'closed' in filter_projects:
            queuedflag=True
        if 'ongoing' in filter_projects or 'open' in filter_projects or 'review' in filter_projects or 'reception_control' in filter_projects or 'all' in filter_projects or 'closed' in filter_projects:
            openflag=True

        default_start_date=(datetime.datetime.now() - relativedelta(years=2)).strftime("%Y-%m-%d")
        default_end_date=datetime.datetime.now().strftime("%Y-%m-%d")
        start_open_date, end_open_date, start_queue_date, end_queue_date, start_close_date, end_close_date = [None] * 6

        if closedflag:
            end_close_date = self.get_argument('youngest_close_date', default_end_date)
            start_close_date = self.get_argument('oldest_close_date', self._get_two_year_from(end_close_date))
        if queuedflag:
            end_queue_date = self.get_argument('youngest_queue_date', default_end_date)
            start_queue_date = self.get_argument('oldest_queue_date', self._get_two_year_from(end_queue_date))
        if openflag:
            end_open_date = self.get_argument('youngest_open_date', default_end_date)
            start_open_date = self.get_argument('oldest_open_date', self._get_two_year_from(end_open_date))

        summary_view = self.application.projects_db.view("project/summary_status", descending=True)

        # view_calls collects http requests to statusdb for each status requested
        view_calls = []
        if filter_projects[:1] != 'P':
            if 'all' in filter_projects:
                view_calls.append(summary_view)
            else:
                statusdb_statuses = set()
                # Need special treatment for these as they are not actual statuses
                if 'review' in filter_projects or 'open' in filter_projects:
                    statusdb_statuses.add('ongoing')
                    statusdb_statuses.add('reception control')
                for status in filter_projects.split(','):
                    status = status.replace('_', ' ')
                    if status in ['aborted', 'closed', 'ongoing', 'pending', 'reception control']:
                        statusdb_statuses.add(status)

                for status in statusdb_statuses:
                    view_calls.append(summary_view[[status, 'Z']:[status, '']])

        filtered_projects = []

        # Specific list of projects given
        if filter_projects[:1] == 'P':
            fprojs = filter_projects.split(',')
            for p_id, p_info in projects.items():
                if p_id in fprojs:
                    filtered_projects[p_id] = p_info

        # Filter aborted projects if not All projects requested: Aborted date has
        # priority over everything else.
        else:
            # Loop over each row from the different view calls
            for row in itertools.chain.from_iterable(view_calls):
                p_info=row.value
                ptype=p_info['details'].get('type')

                if not (projtype == 'All' or  ptype == projtype):
                    continue

                closed_condition, queued_condition, open_condition, queued_proj = [False] * 4

                if 'close_date' in p_info:
                    closed_condition = p_info['close_date'] >= str(start_close_date) and p_info['close_date'] <= str(end_close_date)

                if 'queued' in p_info['details']:
                    queued_proj = True
                    queued_condition = p_info['details'].get('queued') >= str(start_queue_date) and p_info['details'].get('queued') <= str(end_queue_date)

                elif 'project_summary' in p_info and 'queued' in p_info['project_summary']:
                    queued_proj =True
                    queued_condition = p_info['project_summary'].get('queued') >= str(start_queue_date) and p_info['project_summary'].get('queued') <= str(end_queue_date)

                if 'open_date' in p_info:
                    open_condition = p_info['open_date'] >= str(start_open_date) and p_info['open_date'] <= str(end_open_date)

                #Filtering projects
                #aborted projects
                if ('aborted' in filter_projects or filter_projects == 'all') and ('aborted' in p_info['details'] or ('project_summary' in p_info and 'aborted' in p_info['project_summary'])):
                        filtered_projects.append(row)
                #pending reviews projects
                elif ('review' in filter_projects or filter_projects == 'all') and 'pending_reviews' in p_info:
                    filtered_projects.append(row)
                #closed projects
                elif (closedflag or filter_projects == 'all') and closed_condition:
                    filtered_projects.append(row)
                #open projects
                elif openflag and open_condition:
                    if filter_projects == 'all':
                        filtered_projects.append(row)
                    elif 'open' in filter_projects:
                        filtered_projects.append(row)
                    #ongoing projects
                    elif 'ongoing' in filter_projects and queuedflag and queued_condition and not 'close_date' in p_info:
                        filtered_projects.append(row)
                    elif 'reception_control' in filter_projects and not queuedflag and not queued_proj:
                        #reception control projects
                        filtered_projects.append(row)
                #pending projects
                elif ('pending' in filter_projects or filter_projects == 'all') and not 'open_date' in p_info:
                    filtered_projects.append(row)

        final_projects = OrderedDict()
        for row in filtered_projects:
            row = self.project_summary_data(row)
            proj_id = row.key[1]

            final_projects[proj_id] = row.value
            for date_type, date in row.value['summary_dates'].items():
                final_projects[proj_id][date_type] = date

            for key, value in def_dates_gen.items():
                start_date = value[0]
                end_date = value[1]
                final_projects[proj_id][key] = self._calculate_days_in_status(final_projects[proj_id].get(start_date),
                                                                                final_projects[proj_id].get(end_date))

            for key, value in def_dates_summary.items():
                if key == 'days_seq_start':

                    if 'by user' in final_projects[proj_id].get('library_construction_method', '-').lower():
                        start_date = value[0][1]
                    else:
                        start_date = value[0][0]
                else:
                    start_date = value[0]
                end_date = value[1]
                if key in ['days_prep', 'days_prep_start'] and 'by user' in final_projects[proj_id].get('library_construction_method', '-').lower():
                    final_projects[proj_id][key] = '-'
                else:
                    final_projects[proj_id][key] = self._calculate_days_in_status(final_projects[proj_id].get(start_date),
                                                                                final_projects[proj_id].get(end_date))

        return final_projects

    def list_project_fields(self, undefined=False, project_list='all'):
        # If undefined=True is given, only return fields not in columns defined
        # in constants in StatusDB
        columns = self.application.genstat_defaults.get('pv_columns')
        project_list = self.list_projects(filter_projects=project_list)
        field_items = set()
        for project_id, value in project_list.items():
            for key, _ in value.items():
                field_items.add(key)
        if undefined:
            for column_category, column_dict in columns.items():
                field_items = field_items.difference(set(column_dict.values()))
        return field_items

    def search_project_names(self, search_string=''):
        if len(search_string) == 0:
            return ''
        projects = []

        # The list of projects is cached for speed improvement
        t_threshold = datetime.datetime.now() - relativedelta(minutes=3)
        if ProjectsBaseDataHandler.cached_search_list is None or \
                ProjectsBaseDataHandler.search_list_last_fetched < t_threshold:
            projects_view = self.application.projects_db.view("projects/name_to_id_cust_ref", descending=True)

            ProjectsBaseDataHandler.cached_search_list = [(row.key, row.value) for row in projects_view]
            ProjectsBaseDataHandler.search_list_last_fetched = datetime.datetime.now()

        search_string = search_string.lower().strip()

        for row_key, row_value in ProjectsBaseDataHandler.cached_search_list:
            if search_string in row_key.lower() or search_string in row_value[0].lower() or (row_value[1] and search_string in row_value[1].lower()):
                project = {
                    "url": '/project/'+row_value[0],
                    "name": "{} ({})".format(row_key, row_value[0])
                }
                projects.append(project)

        return projects

def prettify_css_names(s):
    return s.replace("(","_").replace(")", "_")

class ProjectsDataHandler(ProjectsBaseDataHandler):
    """ Serves brief information for project in the database.

    Loaded through /api/v1/projects
    """
    def get(self):
        self.set_header("Content-type", "application/json")
        self.write(json.dumps(self.list_projects(self.get_argument('list', 'all'))))

class ProjectsFieldsDataHandler(ProjectsBaseDataHandler):
    """ Serves all fields occuring in the values of the ProjectsDataHandler
    json object.

    Loaded through /api/v1/projects_fields
    """
    def get(self):
        undefined = self.get_argument("undefined", "False")
        undefined = (undefined.lower() == "true")
        project_list = self.get_argument("project_list", "all")
        field_items = self.list_project_fields(undefined=undefined, project_list=project_list)
        self.write(json.dumps(list(field_items)))

class ProjectsSearchHandler(ProjectsBaseDataHandler):
    """ Searches for projects matching the supplied search string

    Loaded through /api/v1/project_search/([^/]*)$
    """
    def get(self, search_string):
        self.set_header("Content-type", "application/json")
        self.write(json.dumps(self.search_project_names(search_string)))

class ProjectDataHandler(ProjectsBaseDataHandler):
    """ Serves brief information of a given project.

    Loaded through /api/v1/project_summary/([^/]*)$
    """
    def get(self, project):
        self.set_header("Content-type", "application/json")
        self.write(json.dumps(self.project_info(project)))

    def project_info(self, project):
        view = self.application.projects_db.view("project/summary")["open", project]
        if not view.rows:
            view = self.application.projects_db.view("project/summary")["closed", project]
        if not len(view.rows) == 1:
            return {}

        summary_row = view.rows[0]
        summary_row = self.project_summary_data(summary_row)

        date_view = self.application.projects_db.view("project/summary_dates",
                                                      descending=True,
                                                      group_level=1)
        date_result = date_view[[project + 'ZZZZ']:[project]]

        summary_row.value['_doc_id'] = summary_row.id

        if date_result.rows:
            for date_row in date_result.rows:
                for date_type, date in date_row.value.items():
                    summary_row.value[date_type] = date
        summary_row.value['sourcedb_url'] = 'http://' + self.settings['couch_server'].split('@')[1]
        return summary_row.value

class ProjectSamplesDataHandler(SafeHandler):
    """ Serves brief info about all samples in a given project.

    Loaded through /api/v1/project/([^/]*)$
    """
    def sample_data(self, sample_data,project, sample):
        sample_data["sample_run_metrics"] = []
        sample_data["prep_status"] = []
        sample_data["prep_finished_date"] = []
        sample_data["run_metrics_data"]={}
        if "library_prep" in sample_data:
            for lib_prep in sorted(sample_data["library_prep"]):
                content=sample_data["library_prep"][lib_prep]
                if "sample_run_metrics" in content:
                    for run, id in content["sample_run_metrics"].items():
                        sample_data["sample_run_metrics"].append(run)
                        sample_data["run_metrics_data"][run]=self.get_sample_run_metrics(run)
                if "prep_status" in content:
                    if content["prep_status"] == "PASSED":
                        sample_data["prep_status"].append(content["prep_status"])
                    else:
                        sample_data["prep_status"].append("FAILED")
                if "prep_finished_date" in content:
                    sample_data["prep_finished_date"].append(content["prep_finished_date"])
                if "library_validation" in content:
                    for agrId, libval in content["library_validation"].items():
                        if "caliper_image" in libval:
                            sample_data['library_prep'][lib_prep]['library_validation'][agrId]['caliper_image'] = self.reverse_url('CaliperImageHandler', project, sample, 'libval{0}'.format(lib_prep))

        if "details" in sample_data:
            for detail_key, detail_value in sample_data["details"].items():
                sample_data[detail_key] = detail_value
        if 'initial_qc' in sample_data:
            if "caliper_image" in sample_data['initial_qc']:
                #Go grab the image from the sftp server
                sample_data['initial_qc']['caliper_image'] = self.reverse_url('CaliperImageHandler', project, sample, 'initial_qc')
            if "frag_an_image" in sample_data['initial_qc']:
                #Go grab the image from the sftp server
                sample_data['initial_qc']['frag_an_image'] = self.reverse_url('FragAnImageHandler', project, sample, 'initial_qc')
        return sample_data

    def list_samples(self, project):
        samples = OrderedDict()
        sample_view = self.application.projects_db.view("project/samples")
        result = sample_view[project]
        # Not all projects (i.e Pending projects) have samples!
        samples = result.rows[0].value if result.rows[0].value else {}
        output = OrderedDict()
        for sample, sample_data in sorted(samples.items(), key=lambda x: x[0]):
            sample_data = self.sample_data(sample_data, project, sample)
            output[sample] = sample_data
        return output

    def get_sample_run_metrics(self, metrics_id):
        data={}
        metrics_view=self.application.samples_db.view('sample/INS_metrics')
        if len(metrics_view[metrics_id].rows)>1:
            application_log.warn("More than one metrics doc found for id {0}".format(metrics_id))

        for row in metrics_view[metrics_id]:
            data=row.value

        return data

    def get(self, project):
        self.set_header("Content-type", "application/json")
        self.write(json.dumps(self.list_samples(project), default=dthandler))

    def sample_list(self, project):
        sample_view = self.application.projects_db.view("project/samples")
        result = sample_view[project]

        samples = result.rows[0].value
        samples = OrderedDict(sorted(samples.items(), key=lambda x: x[0]))
        return samples

class FragAnImageHandler(SafeHandler):
    """serves fragment_analyzer images based on their API uri"""

    def get(self, project, sample, step):
        self.set_header("Content-type", "application/json")
        sample_view = self.application.projects_db.view("project/frag_an_links")
        result = sample_view[project]
        try:
            data = result.rows[0].value
        except TypeError:
            #can be triggered by the data.get().get() calls.
            self.write("no Fragment Analyzer image found")
        else:
            self.write(self.get_frag_an_image(data.get(sample).get(step)))

    @staticmethod
    def get_frag_an_image(url):
        data = lims.get_file_contents(uri=url)
        encoded_string = base64.b64encode(data.read()).decode('utf-8')
        returnHTML=json.dumps(encoded_string)
        return returnHTML

class CaliperImageHandler(SafeHandler):
    """serves caliper images in img tags, with ice and a small umbrella.

    Called through /api/v1/caliper_images/PROJECT/SAMPLE/STEP"""

    def get(self, project, sample, step):
        self.set_header("Content-type", "application/json")
        sample_view = self.application.projects_db.view("project/caliper_links")
        result = sample_view[project]
        try:
            data = result.rows[0].value
        except TypeError:
            #can be triggered by the data.get().get() calls.
            raise tornado.web.HTTPError(404, reason='No caliper image found')
        else:
            self.write(self.get_caliper_image(data.get(sample).get(step)))

    @staticmethod
    def get_caliper_image(url):
        """returns a base64 string of the caliper image asked"""
        #url pattern: sftp://ngi-lims-prod.scilifelab.se/home/glsftp/clarity/year/month/processid/artifact-id-file-id.png
        artifact_attached_id= url.rsplit('.', 1)[0].rsplit('/', 1)[1].rsplit('-', 2)[0]
        #Couldn't use fileid in the url directly as it was sometimes wrong
        caliper_file_id = Artifact(lims=lims, id=artifact_attached_id).files[0].id
        try:
            my_file = lims.get_file_contents(id=caliper_file_id)
            encoded_string = base64.b64encode(my_file.read()).decode('utf-8')
            returnHTML=json.dumps(encoded_string)
            return returnHTML
        except Exception as message:
            print(message)
            raise tornado.web.HTTPError(404, reason='Error fetching caliper images')

class ImagesDownloadHandler(SafeHandler):
    """serves caliper/fragment_analyzer images in a zip archive.
    Called through /api/v1/download_images/PROJECT/[image_type]
    where image types are currently frag_an, caliper_libval or caliper_initial_qc
    """

    def post(self, project, type):
        from io import BytesIO
        import zipfile as zp

        name = ''
        if type == 'frag_an':
            view = 'project/frag_an_links'
            name = 'InitialQCFragmentAnalyser'
        else:
            view = 'project/caliper_links'
            if 'libval' in type:
                name = 'LibraryValidationCaliper'
            elif 'initial_qc' in type:
                name = 'InitialQCCaliper'

        sample_view = self.application.projects_db.view(view)
        result = sample_view[project]
        try:
            data = result.rows[0].value
        except TypeError:
            #can be triggered by the data.get().get() calls.
            raise tornado.web.HTTPError(404, reason='No caliper image found')

        fileName = '{}_{}_images.zip'.format(project, name)
        f = BytesIO()
        num_files = 0
        with zp.ZipFile(f, "w") as zf:
            for sample in data:
                if data[sample]:
                    num_files +=1
                    for key in data[sample]:
                        img_file_name = sample + '.'
                        if 'frag_an' in type and 'initial_qc' in key:
                            img_file_name = img_file_name + 'png'
                            zf.writestr(img_file_name, base64.b64decode(FragAnImageHandler.get_frag_an_image(data[sample][key])))
                        elif 'caliper' in type:
                            checktype = type.split('_',1)[1]
                            if 'libval' in checktype:
                                img_file_name = img_file_name + key + '.'
                            if checktype in key:
                                img_file_name = img_file_name + data[sample][key].rsplit('.')[-1]
                                zf.writestr(img_file_name, base64.b64decode(CaliperImageHandler.get_caliper_image(data[sample][key])))

        if num_files == 0:
            raise tornado.web.HTTPError(status_code=404, log_message='No files!', reason='No files to be downloaded!!')
        self.set_header('Content-Type', 'application/zip')
        self.set_header('Content-Disposition', 'attachment; filename={}'.format(fileName))
        self.write(f.getvalue())
        f.close()
        self.finish()

class ProjectSamplesHandler(SafeHandler):
    """ Serves a page which lists the samples of a given project, with some
    brief information for each sample.
    URL: /project/([^/]*)
    """

    def get(self, project):
        t = self.application.loader.load("project_samples.html")
        worksets_view = self.application.worksets_db.view("project/ws_name", descending=True)
        # to check if multiqc report exists (get_multiqc() is defined in util.BaseHandler)
        multiqc = self.get_multiqc(project) or ''
        self.write(t.generate(gs_globals=self.application.gs_globals, project=project,
                              user=self.get_current_user(),
                              columns = self.application.genstat_defaults.get('pv_columns'),
                              columns_sample = self.application.genstat_defaults.get('sample_columns'),
                              lims_dashboard_url = self.application.settings['lims_dashboard_url'],
                              prettify = prettify_css_names,
                              worksets=worksets_view[project],
                              multiqc=multiqc,
                              lims_uri=BASEURI
                              ))

class ProjectsHandler(SafeHandler):
    """ Serves a page with project presets listed, along with some brief info.
    URL: /projects
    """
    def get(self, projects='all'):
    #def get(self):
        t = self.application.loader.load("projects.html")
        columns = self.application.genstat_defaults.get('pv_columns')
        self.write(t.generate(gs_globals=self.application.gs_globals, columns=columns, projects=projects, user=self.get_current_user()))

class RunningNotesDataHandler(SafeHandler):
    """Serves all running notes from a given project.
    It connects to LIMS to fetch and update Running Notes information.
    URL: /api/v1/running_notes/([^/]*)
    """
    def get(self, project):
        self.set_header("Content-type", "application/json")
        v = self.application.projects_db.view("project/project_id")
        if len(v[project]) == 0:
            raise tornado.web.HTTPError(404, reason='Project not found: {}'.format(project))
        else:
            for row in v[project]:
                doc_id = row.value
            doc = self.application.projects_db.get(doc_id)
            # Sorted running notes, by date
            running_notes = json.loads(doc['details'].get('running_notes', '{}'))
            sorted_running_notes = OrderedDict()
            for k, v in sorted(running_notes.items(), key=lambda t: t[0], reverse=True):
                sorted_running_notes[k] = v
            self.write(sorted_running_notes)

    def post(self, project):
        note = self.get_argument('note', '')
        category = self.get_argument('category', '')
        user = self.get_current_user()
        if not note:
            self.set_status(400)
            self.finish('No project id or note parameters found')
        else:
            newNote = RunningNotesDataHandler.make_project_running_note(self.application, project, note, category, user.name, user.email)
            self.set_status(201)
            self.write(json.dumps(newNote))

    @staticmethod
    def make_project_running_note(application, project, note, category, user, email):
        timestamp = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')
        newNote = {'user': user, 'email': email, 'note': note, 'category' : category, 'timestamp': timestamp}

        #get and save running notes directly in genstat.
        v = application.projects_db.view("project/project_id")
        for row in v[project]:
            doc_id = row.value
        doc = application.projects_db.get(doc_id)

        running_notes = json.loads(doc['details'].get('running_notes', '{}'))
        running_notes.update({timestamp: newNote})
        project_name = doc['project_name']
        proj_ids = [project, project_name]
        doc['details']['running_notes'] = json.dumps(running_notes)
        application.projects_db.save(doc)

        #check if it was saved
        doc = application.projects_db.get(doc_id)
        assert (doc['details']['running_notes'] == json.dumps(running_notes)), "The Running note wasn't saved in StatusDB!"

        #### Check and send mail to tagged users
        pattern = re.compile("(@)([a-zA-Z0-9.-]+)")
        userTags = [x[1] for x in pattern.findall(note)]
        if userTags:
            RunningNotesDataHandler.notify_tagged_user(application, userTags, proj_ids, note, category, user, timestamp, 'userTag')
        ####
        ##Notify proj coordinators for all project running notes
        proj_coord = '.'.join(doc['details'].get('project_coordinator','').lower().split())
        if proj_coord and proj_coord not in userTags and proj_coord!=email.split('@')[0]:
            RunningNotesDataHandler.notify_tagged_user(application, [proj_coord], proj_ids, note, category, user, timestamp, 'creation')
        return newNote

    @staticmethod
    def notify_tagged_user(application, userTags, project, note, category, tagger, timestamp, tagtype):
        view_result = {}
        project_id = project[0]
        project_name = project[1]
        time_in_format = datetime.datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').strftime("%a %b %d %Y, %I:%M:%S %p")
        note_id = 'running_note_' + project_id + '_' + \
                    str(int((datetime.datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S') -  datetime.datetime.utcfromtimestamp(0)).total_seconds()))
        for row in application.gs_users_db.view('authorized/users'):
            if row.key != 'genstat_defaults':
                view_result[row.key.split('@')[0]] = row.key
        if category:
            category = ' - ' + category

        if tagtype=='userTag':
            notf_text = 'tagged you'
            slack_notf_text = 'You have been tagged by *{}* in a running note'.format(tagger)
            email_text = 'You have been tagged by {} in a running note'.format(tagger)
        else:
            notf_text = 'created note'
            slack_notf_text = 'Running note created by *{}*'.format(tagger)
            email_text = 'Running note created by {}'.format(tagger)

        for user in userTags:
            if user in view_result:
                option = PresetsHandler.get_user_details(application, view_result[user]).get('notification_preferences', 'Both')
                #Adding a slack IM to the tagged user with the running note
                if option == 'Slack' or option == 'Both':
                    nest_asyncio.apply()
                    client = slack.WebClient(token=application.slack_token)
                    notification_text = '{} has {} in {}, {}!'.format(tagger, notf_text, project_id, project_name)
                    blocks = [
                        {
                        "type": "section",
		                "text": {
                            "type": "mrkdwn",
        		            "text": ("_{} for the project_ "
                                     "<{}/project/{}#{}|{}, {}>! :smile: \n_The note is as follows:_ \n\n\n")
                             .format(slack_notf_text, application.settings['redirect_uri'].rsplit('/',1)[0], project_id, note_id, project_id, project_name)
                             }
                        },
                        {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": ">*{} - {}{}*\n>{}\n\n\n\n _(Please do not respond to this message here in Slack."
                            " It will only be seen by you.)_".format(tagger, time_in_format, category, note.replace('\n', '\n>'))
         	                  }
                        }
                    ]

                    try:
                        userid = client.users_lookupByEmail(email=view_result[user])
                        channel = client.conversations_open(users=userid.data['user']['id'])
                        client.chat_postMessage(channel=channel.data['channel']['id'], text=notification_text, blocks=blocks)
                        client.conversations_close(channel=channel.data['channel']['id'])
                    except Exception:
                        #falling back to email
                        option = 'E-mail'

                #default is email
                if option == 'E-mail' or option == 'Both':
                    msg = MIMEMultipart('alternative')
                    msg['Subject']='[GenStat] Running Note:{}, {}'.format(project_id, project_name)
                    msg['From']='genomics-status'
                    msg['To'] = view_result[user]
                    text = '{} in the project {}, {}! The note is as follows\n\
                    >{} - {}{}\
                    >{}'.format(email_text, project_id, project_name, tagger, time_in_format, category, note)

                    html = '\
                    \

 \
                    {} in the project {}, {}! The note is as follows
\

\

\

\
                        {} - {} {}\

\

\

{}
\

'.format(email_text, application.settings['redirect_uri'].rsplit('/',1)[0],
                    project_id, note_id, project_id, project_name, tagger, time_in_format, category, markdown.markdown(note))

                    msg.attach(MIMEText(text, 'plain'))
                    msg.attach(MIMEText(html, 'html'))

                    s = smtplib.SMTP('localhost')
                    s.sendmail('genomics-bioinfo@scilifelab.se', msg['To'], msg.as_string())
                    s.quit()

class LinksDataHandler(SafeHandler):
    """ Serves external links for each project
        Links are stored as JSON in LIMS / project
        URL: /api/v1/links/([^/]*)
    """

    def get(self, project):
        self.set_header("Content-type", "application/json")
        p = Project(lims, id=project)
        p.get(force=True)

        links = json.loads(p.udf['Links']) if 'Links' in p.udf else {}

        #Sort by descending date, then hopefully have deviations on top
        sorted_links = OrderedDict()
        for k, v in sorted(links.items(), key=lambda t: t[0], reverse=True):
            sorted_links[k] = v
        sorted_links = OrderedDict(sorted(sorted_links.items(), key=lambda k: k[1]['type']))
        self.write(sorted_links)

    def post(self, project):
        user = self.get_current_user()
        a_type = self.get_argument('type', '')
        title = self.get_argument('title', '')
        url = self.get_argument('url', '')
        desc = self.get_argument('desc','')

        if not a_type or not title:
            self.set_status(400)
            self.finish('Link title and type is required')
        else:
            p = Project(lims, id=project)
            p.get(force=True)
            links = json.loads(p.udf['Links']) if 'Links' in p.udf else {}
            links[str(datetime.datetime.now())] = {'user': user.name,
                                                   'email': user.email,
                                                   'type': a_type,
                                                   'title': title,
                                                   'url': url,
                                                   'desc': desc}
            p.udf['Links'] = json.dumps(links)
            p.put()
            self.set_status(200)
            #ajax cries if it does not get anything back
            self.set_header("Content-type", "application/json")
            self.finish(json.dumps(links))

class ProjectTicketsDataHandler(SafeHandler):
    """ Return a JSON file containing all the tickets in ZenDesk related to this project
    URL: /api/v1/project/([^/]*)/tickets
    """
    def get(self, p_id):
        self.set_header("Content-type", "application/json")
        p_name = self.get_argument('p_name', False)
        if not p_name:
            self.set_status(400)
            self.finish('No project name specified!')

        try:
            #Search for all tickets with the given project name
            total_tickets = OrderedDict()
            for ticket in self.application.zendesk.search(query='fieldvalue:"{}"'.format(p_name)):
                total_tickets[ticket.id] = ticket.to_dict()
                for comment in self.application.zendesk.tickets.comments(ticket=ticket.id):
                    if 'comments' not in total_tickets[ticket.id]:
                        total_tickets[ticket.id]['comments']=[{'author':comment.author.name, 'comment': comment.to_dict()}]
                    else:
                        total_tickets[ticket.id]['comments'].extend([{'author':comment.author.name, 'comment': comment.to_dict()}])
            # Return the most recent ticket first
            self.write(total_tickets)
        except ZenpyException:
            self.set_status(400)
            self.finish('There was a problem with ZenDesk connection, please try it again later.')

class ProjectQCDataHandler(SafeHandler):
    """Serves filenames in qc_reports
    URL: /api/v1/projectqc/([^/]*)
    """
    def get(self, projectname):
        paths={}
        prefix=os.path.join(os.getcwd(), 'qc_reports')
        #this should be run_dir
        if re.match("^[A-Z]{1,2}\.[A-Za-z0-9]+\_[0-9]{2}\_[0-9]{2,3}$", projectname):
            qc_location=os.path.join(prefix,projectname)
            cursample=''
            currun=''
            for root, subdirs, files in os.walk(qc_location):
                rootname=os.path.basename(root)
                if re.match("^P[0-9]{3,5}\_[0-9]{3,4}$",rootname):
                    paths[rootname]={}
                    cursample=rootname
                    currun=''

                elif re.match("^[0-9]+\_[A-Z0-9]+$",rootname):
                    paths[cursample][rootname]=[]
                    currun=rootname

                for f in files:
                    try:
                        paths[cursample][currun].append(os.path.relpath(os.path.join(root,f), prefix))
                    except KeyError:
                        print("cannot add {} to paths, one of these two keys does not exist: sample->{} run->{}".format(os.path.relpath(os.path.join(root,f), prefix), cursample, currun))
        self.set_header("Content-type", "application/json")
        self.write(json.dumps(paths))

class CharonProjectHandler(SafeHandler):
    """queries charon about the current project"""
    def get(self, projectid):
        try:
            url="{}/api/v1/summary?projectid={}".format(self.application.settings['charon']['url'], projectid)
            headers = {'X-Charon-API-token': '{}'.format(self.application.settings['charon']['api_token'])}
            project_url="{}/api/v1/project/{}".format(self.application.settings['charon']['url'], projectid)
        except KeyError:
            url="https://charon.scilifelab.se/api/v1/summary?projectid={}".format(projectid)
            project_url="https://charon.scilifelab.se/api/v1/project/{}".format(projectid)
            headers={}
        r = requests.get(url, headers = headers )
        if r.status_code == requests.status_codes.codes.OK:
            self.write(r.json())
        else:
            self.set_status(400)
            self.finish('There was a problem connecting to charon, please try it again later. {}'.format(r.reason))

class RecCtrlDataHandler(SafeHandler):
    """Handler for the reception control view"""
    def get(self, project_id):
        sample_data={}
        #changed from projects due to view timing out with os_process_error
        v = self.application.projects_db.view("samples/rec_ctrl_view")
        for row in v[project_id]:
            sample_data.update(row.value)

        t = self.application.loader.load("rec_ctrl_view.html")
        self.write(t.generate(gs_globals=self.application.gs_globals,
                              project_id=project_id,
                              sample_data=sample_data,
                              json_data=json.dumps(sample_data),
                              user=self.get_current_user()))

class ProjMetaCompareHandler(SafeHandler):
    """Handler for the project meta comparison page view"""
    def get(self):
        pids = self.get_arguments("p")
        t = self.application.loader.load("proj_meta_compare.html")
        self.write(t.generate(gs_globals=self.application.gs_globals, pids=pids,
                              user=self.get_current_user()))

class ProjectRNAMetaDataHandler(SafeHandler):
    """Handler to serve RNA metadata from project"""
    def get(self, project_id):
        data="{}"
        view=self.application.analysis_db.view("reports/RNA_report")
        for row in view[project_id]:
            data=json.dumps(row.value)

        self.set_status(200)
        self.set_header("Content-type", "application/json")
        self.write(data)

class ProjectInternalCostsHandler(SafeHandler):
    """Handler for the update of the project internal costs"""
    def post(self, project_id):
        try:
            data = json.loads(self.request.body)
            text = data.get('text', '')
            p = Project(lims, id=project_id)
            p.udf['Internal Costs'] = text
            p.put()
            view = self.application.projects_db.view("project/project_id")
            for row in view[project_id]:
                doc=self.application.projects_db.get(row.id)
                doc['details']['internal_costs']=text
                self.application.projects_db.save(doc)

        except Exception as e:
            self.set_status(400)
            self.finish('
could not update Entity {} :

{}
'.format( project_id, e))
        else:
            self.set_status(200)
            self.set_header("Content-type", "application/json")
            self.write(self.request.body)

class PrioProjectsTableHandler(SafeHandler):
    """Serves information about projects statuses.

    Loaded through /api/v1/prio_projects"""

    def get(self):
        projects = {}
        def_dates_rec_ctrl = { 'days_recep_ctrl' : ['open_date', 'queued']
                             }
        #dates in order
        def_dates_ongoing = { 'days_prep_start' : ['queued', 'library_prep_start'],
                               'days_prep' : ['library_prep_start', 'qc_library_finished'],
                               'days_seq_start' : [['qc_library_finished', 'queued'], 'sequencing_start_date'],
                               'days_seq' : ['sequencing_start_date', 'all_samples_sequenced'],
                               'days_analysis' : ['all_samples_sequenced', 'best_practice_analysis_completed'],
                               'days_data_delivery' : ['best_practice_analysis_completed', 'all_raw_data_delivered'],
                               'days_close' : ['all_raw_data_delivered', 'close_date']
                             }

        statuses = ['ongoing', 'reception control']
        view_calls = []

        view = self.application.projects_db.view("project/summary_status", descending=True)
        for status in statuses:
            view_calls.append(view[[status, 'Z']:[status, '']])
        for row in itertools.chain.from_iterable(view_calls):
            proj_id_name_lib = row.value['project_name']+' ('+row.key[1]+')'+'| '+row.value['details'].get('library_construction_method', '-')
            proj_val = row.value
            for date_type, date in proj_val['summary_dates'].items():
                proj_val[date_type] = date
            if row.key[0] == 'ongoing':
                for k, v in proj_val['project_summary'].items():
                    proj_val[k] = v

            is_fin_lib = False
            if 'by user' in proj_val.get('library_construction_method', '-').lower():
                is_fin_lib = True

            for key, value in def_dates_rec_ctrl.items():
                start_date = value[0]
                end_date = value[1]
                date_val = self._calculate_days_in_status(proj_val.get(start_date),
                                                               proj_val.get(end_date))
                projects[proj_id_name_lib] = { key: date_val }

            if row.key[0] == 'ongoing':
                for key, value in def_dates_ongoing.items():
                    if key == 'days_seq_start':
                        if is_fin_lib:
                            start_date = value[0][1]
                        else:
                            start_date = value[0][0]
                    else:
                        start_date = value[0]
                        end_date = value[1]
                    if key in ['days_prep', 'days_prep_start'] and is_fin_lib:
                        date_val = 0
                    else:
                        date_val = self._calculate_days_in_status(proj_val.get(start_date),
                                                                       proj_val.get(end_date))

                    projects[proj_id_name_lib][key] = date_val

        #Delete statuses with 0 days
        for k,v in projects.items():
            min_days = 0
            #turn v into list to avoid 'dictionary changed size during iteration'- error
            for k2 in list(v):
                if v[k2] <= min_days:
                    if k2 == 'days_recep_ctrl':
                        continue
                    else:
                        del projects[k][k2]

        #Get list of projects with status and days containing only last status
        t_data = []
        for k,v in projects.items():
            stat = list(v)[-1]
            t_data.append((k, stat, v[stat]))
        #Sort projects on number of days
        t_data.sort(key=lambda x:x[2], reverse=True)

        self.set_header("Content-type", "application/json")
        self.write(json.dumps(t_data[:50]))

    def _calculate_days_in_status(self, start_date, end_date):
        days = 0
        if start_date:
            if end_date:
                delta = dateutil.parser.parse(end_date) - dateutil.parser.parse(start_date)
            else:
                delta = datetime.datetime.now() - dateutil.parser.parse(start_date)
            days = delta.days
        return days

redpajama_set_name: RedPajamaGithub


text:
Electrical Engineering Equipment Fabrication & Quality News Piping Valves All Calculators & Tools
Sponsor this post
Fabrication & Quality
Satti
The thinnest material known to man, graphene provides extreme strength, thermal conductivity, and electrical conductivity on par with copper. As such, this material has made an uproar with the scientific community and paved the way to mind-blowing applications such as bulletproof armor, graphene circuitry, ultra-light space crafts, healthcare and medicine, and even the construction of space elevators. In this article, you will learn about the properties of graphene, common applications, and production methods.
The commonly used material graphite is composed of millions of layers of graphene stacked onto each other. Graphene is a single layer of atoms arranged in a two-dimensional lattice. And this lattice produces a very strong yet flexible bond of carbon atoms. Thus, graphene retains the strength of diamonds with high bendability.
Due to the nanoscale of graphene layer, it is very light and much stronger than structural steel. It can also stretch up to 20% of its initial size without breaking, thus, making it flexible. The atoms in the material also delocalize electrons which makes it so good at conducting electricity and heat.
Courtesy: Libre Texts
Common Applications
Common applications include mechanical reinforcement, electronics, energy storage, and anti-corrosion capability.
Mechanical Reinforcement
Graphene enhances the strength of materials such as plastics, rubber, steel beams, and other materials. By blending it with the subject material, high-temperature capabilities, and high tensile strength result. Due to its ultra-lightweight, vehicle production and aerospace applications such as aircraft and space ships are common applications. Product designers also blend the material into everyday items such as tennis rackets, bike wheels and frames, sunglasses, and even mobile phone parts.
Graphene's two-dimensional lattice lets electrons pass through them at the speed of light. This makes it potentially useful to replace silicon-based transistors which will make PLCs and computers a lot faster at handling and processing data. Overall, electronics should be able to operate at high frequencies increasing computational capacity, although commercial applications are still at the nascent stage.
Another application of graphene is that it can be used as superconductors. Superconductors are a substance that conducts electricity without resistance. Since electrons move freely through the material, it is a potential material to use in batteries which makes them store more energy and charge faster too. Graphene batteries provide the same operating principle as traditional batteries with two electrodes and an electrolyte solution. The main difference lies in the material composition of the graphene-infused cathode.
Thermal Conductivity
Aside from being incredibly strong and lightweight, graphene provides great thermal conductivity. It can be a great material for heat spreading solutions such as heat sinks and heat dissipation films. Sheets of the substance provide ultra-thin touch screens, lightweight solar panels, and long-lasting LED lights.
Anti-Corrosion Capability
The impermeable structure allows for blending in paint or primers and with just one coating to prevent rust and corrosion. Applications in aluminum protection have shown there's an optimal amount of graphene to be added to the coating. Too high of a concentration actually reduces overall corrosion resistance as a highly conductive circuit forms.
Courtesy: Research Gate
Production Methods
In 2004, Andre Geim and Konstantin Novoselov at the University of Manchester discovered and characterized the material graphene. They created it by using only graphite and sticky tapes. They placed graphite into the tape and folded it in two, and then cleaved the flake in half. After repeating this process a number of times they produced graphite with one single atom of thickness. This ultra-thin graphite became known as graphene. In 2010, Geim and Novoselov received the Nobel Prize for Physics for their discovery.
Today many other production techniques have evolved. Some, such as the commonly used flame synthesis method for nanoparticles do not adapt well to graphene production. Other major production methods include direct liquid-phase exfoliation, oxidation and exfoliation of graphite, chemical vapor deposition, and pulsed laser deposition.
Direct Liquid-Phase Exfoliation of Graphite
Liquid phase exfoliation is a type of mechanical exfoliation. Mechanical exfoliation includes methods such as stirring, shearing forces, ball milling, and using ultrasound on graphite suspended in a liquid. This method generally is not suitable for large-scale production.
Oxidation of Graphite and Exfoliation and/or Reduction
Another approach to exfoliation is through the use of oxidizing agents such as sulfuric and nitric acids. The addition of acid oxidizes graphene sheets by forcing oxygen atoms between the sheets of graphene thus causing them to split apart. The resulting graphene oxide sheets suspended in acid and water and when filtered, leave flakes of graphene oxide. The final step removes oxygen and leaves a final product of pure graphene coating. This resulting material is called reduced graphene oxide or RGO.
Chemical Vapor Deposition (CVD)
In this approach, a chamber filled with gas such as methane heats a copper plate to 1000 degrees Celsius (1832 F) The methane gives up carbon atoms to the carbon plate, and the graphene self assembles itself atom by atom onto the copper plate. This method takes a lot of time to produce a small amount sheet and provides a lower quality outcome than exfoliation methods.
Pulsed Laser Deposition (PLD)
PLD provides a laser energy source outside of a chamber and the chamber maintains an ultrahigh vacuum. Material deposits via stoichiometry transfer on a target surface. PLD provides a low-temperature growth rate and thus high-quality graphene.
Previous PostPrevious Flame Arrestor
Next PostNext Material Hardness
Get the latest PL0 updates in your inbox
PUNCHLIST ZERO
About Us Contact Us Twitter
Electrical Piping & Vessels Project Management General Use
Reference Books Improve Your Org Culture

redpajama_set_name: RedPajamaCommonCrawl


This 2,789-seat worship center was designed as part of a campus master plan. A large platform stage with a 240-seat choir loft is the focal point of the facility that features terraced seating and the latest in audio/visual technologies. Covered walkways link the worship center to the various other buildings on the campus, while courtyards provide useable outdoor spaces. A central lobby provides pre-function space and features a café.


text:
Home Crypto Payments
Glance to license payment tech including Glance Coin platform to Fobi Pay
Glance Technologies Inc. today announced that it has agreed to grant Fobi Pay Technologies Inc., a non-exclusive license to use the Glance Pay mobile payment, security, anti-fraud and Glance Coin blockchain technology for a $5 million fee for a one-year initial term, renewable annually for $10,000 per year.
"Fobi Pay will offer big data analytics for networks of related businesses. Our value prop is in merging purchase basket and historical data with social media and mobile data to create highly personalized customer journeys," says Rob Anson, CEO of Fobi Pay. "By licensing the Glance technology we will be able to offer a closed loop digital media and mobile strategy for our industry partners."
IDC says that worldwide revenues for big data and business analytics will grow from $130.1 billion in 2016 to more than $203 billion in 2020 and that "data monetization" will become a major source of revenues, as the world will create 180 zettabytes of data (or 180 trillion gigabytes) in 2025, up from less than 10 zettabytes in 2015.
Fobi Pay is also licensing big data analytics, social wifi and their real-time offers engine platform from Fobisuite Technologies Inc. By combining the Glance and Fobisuite technologies, Fobi Pay will develop the technology for servicing a network of merchants localized around a common theme such as shopping centers, professional sports leagues, and tourist associations. Fobi Pay has the potential to capture data from itemized receipts, combine it with any preexisting online shopping histories, match to social media profiles and attribute the purchases to an individual shopper.
Fobi Pay's target clients will potentially be able to access big data analyzed and interpreted by artificial intelligence for retailers in the space and combine with a white label version of Glance Pay to allow their shoppers to process in-location mobile payments.
Users of the white label app will potentially have access to Glance's Ethereum blockchain based loyalty rewards program, Glance Coin, as well as receive real-time exclusive and personalized deals and coupons derived from big data analysis.
Additionally, Fobisuite has agreed to grant Glance a license to use Fobisuite's technology of which allows for the digitization of receipts for data collection and the ability to customize and append receipts with advertisements, deals, and coupons for merchants for a fee of $1.5 million.
"Fobisuite's agnostic technology platform will allow us to bypass traditional integrations with POS systems to access real-time bill and payment information, increasing the value of our systems for merchants and improving our merchant adoption rate. Additionally, this opens up further revenue opportunities from real-time targeted advertising and big data analytics. We believe this is complementary to our current Glance Pay platform and are excited to begin implementing this technology."
Glance CEO Desmond Griffin
The following are the material terms of the agreement between Fobi Pay, Glance, and Fobisuite:
Glance will license the Glance Pay mobile payment platform technology to Fobi Pay in exchange for 20,000,000 shares of Fobi Pay, at a deemed price per share of $0.25, for an aggregate deemed value of $5,000,000.
Fobisuite will license all of its technology to Fobi Pay in exchange for 20,500,000 shares of Fobi Pay, at a deemed price per share of $0.25, for an aggregate deemed value of $5,125,000.
Fobisuite will license the technology related to its USB/serial hardware device to Glance for $1,500,000, payable by: (i) the issuance of 250,000 common shares of Glance at a price of $2 per share equal to a value of $500,000, which will be subject to a 4 month hold period; and (ii) $1 million in cash. The license will be for an initial term of ten years with perpetual subsequent one-year renewal terms. This device allows for the digitization of receipts for data collection and the ability to customize and append receipts with advertisements, deals, and coupons for merchants.
Pursuant to its stock option incentive plan, Glance Technologies has granted certain advisors incentive stock options to purchase 650,000 common shares of the company. The stock options are subject to the terms of the company's stock option plan and regulatory approval.


Miracle of Christmas postcard phrase. Handmade typographic lettering. Calligraphic sketch on transparent background.
Stay Cozy. Written phrase, lettering by hand.
2018 2019 Written phrase, lettering by hand.
Winter written phrase, lettering by hand.
Written phrase, lettering by hand.


text:
OUR BOARD + ADVISORS
Solving the organ donor crisis is hard work, so we stacked our team with the smartest, most dedicated people we could find. From Nobel prize-winners to tech geniuses, marketing gurus to surgeons, we're honored to have an incredible groups of women and men on our board and as advisors.
BRYAN SIVAK
Bryan is a healthcare thoughtleader and investor. He was most recently the Entrepreneur-in-Residence at the Robert Wood Johnson Foundation, where he helped build a Culture of Health more effectively. Previously, Bryan served as the CTO of HHS and as a Senior Fellow at Civic Hall.
REGGiE LOVE
Reggie is the former Special Assistant and personal aide to President Barack Obama. He left the White House to pursue an MBA at Wharton, and is now a Partner at Transatlantic Holdings.
Dara Kass
Dara Kass, MD is an emergency medicine physician and advocate for the advancement of women in medicine. She is the founder of FemInEM, an organization dedicated to providing support, community, resources, and opportunities for women working in emergency medicine. In addition to her work as a writer, advocate, and physician, she served as a living organ donor to her son Sammy, the youngest of her 3 children with her husband Michael. She lives with her family in Brooklyn, New York.
Jenna was recently listed as Inc. Magazine's "Top 35 Under 35 Entrepreneurs." She used to be the youngest producer at MTV. She's also a horrible speller, but we forgive her.
Greg got a psych degree from Duke and then went to write at ESPN. He was a Chicago Cubs fan before it was cool, and loves writing short bios about himself.
ROGER ALTMAN
FORMER board member
Roger is the Founder and Executive Chairman of Evercore Partners, the Vice Chairman of the Board of the American Museum of Natural History, and a member of the Steering Committee for the Bilderberg Group. Previously, he served as the United States Deputy Secretary of the Treasury.
Gretchen is a best selling author of The Happiness Project and Happier at Home. She formerly clerked on the US Supreme Court for Justice Sandra Day O'Connor and served as a chief adviser to Federal Communications Commission Chairman Reed Hundt. She has also been a lecturer at the Yale Law School and the Yale School of Management.
Dr. andrew cameron
medical advisor
Dr. Cameron spearheaded the organ donation partnership with Facebook in 2012. He also has the fastest metabolism of anyone we've ever met; he eats chocolate nonstop through every meeting and still weighs about 150 pounds soaking wet.
dr. joshua sonett
Dr. Sonett performed a pleural decortitcation on Bill Clinton to remove scar tissue after his quadruple heart bypass operation. So, you know, he's good with a knife (think its called scalpel).
dr. kenneth moritsugu
Ken is the former Acting Surgeon General of the US, former Chairman of the Johnson & Johnson Diabetes Institute, former Board Director at the United Network for Organ Sharing, and currently a Board Member of the Washington Regional Transplant Consortium.
dr. talia baker
Talia went into labor for her second child during a liver transplant. She was relieved and ran across the street to the women's hospital, where she promptly delivered a beautiful baby girl.
BLAIR SADLER
Blair is Senior Fellow at the Institute for Healthcare Improvement and Past President of the Rady Children's Hospital in San Diego. Also, he married up. His wife Georgia was featured in Malcolm Gladwell's Tipping Point, and is one of the few people who make Greg star struck. She's his Justin Bieber.
Dr. Fred sadler
Fred received the National Kidney Foundation's Distinguished Service Award for his work in co-authoring, along with his twin brother Blair, the Uniform Anatomical Gift Act of 1968. Which is, you know, only the seminal piece of legislation of organ donor registration law in the United States.
kinjil mathur
technology ADVISOR
At 27 she built an algorithm to power a marketing personalization engine that out performed ex-rocket scientists' and other Silicon Valley chief scientists' competing engines. And, her shoe collection is (should be) the envy of every girl in NYC.
stan freck
IIR and Technology advisor
At one point, Stan drove the vision for the world's 4th largest website, which is why he knows more facts about anything/everything than anyone…avoid playing him in trivial pursuit at all costs.
andrew rasiej
He's at the center of all things tech in NYC (and the world, as far as we're concerned) and has perfected the art of making pizza.
After graduating from Yale in 3 years, Mike cracked the original Google algorithm and was eventually offered a slot on the Google Advisory Board. Mike is also in a band, Evil Jake, that toured Europe with the USO, had its music on MTV, and made an appearance in the Jack Black movie School of Rock.
charles bearden
partnerships advisor
Charles is the longest practicing Organ Recovery Coordinator in the United States and has facilitated the recovery and transplantation of over 2500 organs over his 4 decade career. He is also a Knight Commander in the Order of Saint Lazarus in America and leads their Organ Donation Awareness Initiatives.
glenn engler
marketing advisor
Glenn is a regular speaker at digital media conferences, writes for AdAge, and has a bi-weekly podcast called Market Edge. But what he's best known for?… His wife Kim's animal shelter was the location for the infamous Rob Gronkowski kitten-photo shoot.
jamie redford
Jamie has produced some of the coolest documentaries we can think of, including one about dyslexia (a topic close to Jenna's heart), and has the best leather jacket collection we've ever seen. He also knows all the best ski slopes in Utah.
alan pinto
Alan has learned so much about blinding retinal diseases through his charitable work with The Foundation Fighting Blindness that he has been addressed as "Dr. Pinto" at certain events.
nicole patrice de member
Nicole has always had a passion for music, and has played a pivotal role in the electronic dance and hip-hop music industries. In 2000, she founded Effigy Studios—which eventually sold to Eminem—and GrooveTickets, one of the first online ticketing companies.
BABS Burchfield
Babs is the Co-Founder of Conscious Commerce, along with actress Olivia Wilde, which promote socially responsible ventures. Babs is also a producer at Global Citizen Festival, where she has worked with artists such as Jay-Z, Beyonce, Carrie Underwood, Stevie Wonder, and the Black Keys.
iir and marketing advisor
Besides having one of the most popular TED talks EVER with 20 million views, Simon is quite the chef… specializing in chocolate covered bananas.
iir advisor
Al won the 2012 Nobel Prize in Economics "for the theory of stable allocations and the practice of market design," in large part for his work in developing paired kidney donation chains. No one else at ORGANIZE has won the Nobel Prize…
judd kessler
Judd B. Kessler received a B.A. in Economics from Harvard in 2004, an M.Phil. in Economics from Cambridge in 2005, and his Ph.D. in Business Economics from Harvard in 2011. Judd's mentor and publishing partner is Al Roth, winner of the 2012 Nobel Prize in Economic Sciences.
Malay B. Shah
Dr. Shah is a liver transplant surgeon at the University of Kentucky. As a Cincinnati sports fan, he holds the care of the downtrodden and neglected near and dear to his heart.
ALL THE PARTICULARS
To contact us, please email [email protected]
MORE FROM ORGANIZE
If you're interested in hearing more about ORGANIZE, join our quarterly mailing list by entering your email below.


PULLMAN, Wash. (AP) - Malik Hairston sank two 3-point field goals in the final 12 seconds, including the game-winning basket with four-tenths of a second remaining, to lift Oregon to a 52-50 victory over Washington State on Thursday night.
Jordan Kent led Oregon (10-8, 4-2 Pac-10) with season highs of 13 points and 12 rebounds. Hairston scored 12 points, and Aaron Brooks added 10 in Oregon's third win in a row and ninth consecutive victory over Washington State.
Washington State (9-6, 2-4) was led by Josh Akognon with 18 points. Robbie Cowgill scored 12 points and had a team-high five rebounds for the Cougars. WSU was outrebounded 39-22, including 16-4 on the offensive glass.
Neither team led by more than four points. Oregon held a 29-27 lead at the half.
Oregon led 46-44 with 2:55 left before WSU scored six unanswered points. Cowgill hit a hook shot off an Akognon feed with 2:25 left. Cowgill sank a 10-foot jumper with 38.3 seconds to go, then Akognon sank two free throws with 25 seconds left.
Washington State's Ivory Clark missed the front end of a one-and-one free-throw situation with 10.7 seconds left. The Cougars made just 8 of 14 free throws in the game, and Oregon went 7-for-14.
Both teams shot 50 percent from the field in the first half, but dropped below 40 percent in the second half. Oregon shot 42 percent for the game, compared to 44 percent for WSU.
WSU's points and rebounds were season lows. The Ducks tied their season low for points. The total of 102 points was the lowest in the Oregon-Washington State series since the Cougars beat Oregon 48-45 (93 total points) on Feb. 17, 1983.
The loss was Washington State's first in seven games at Friel Court this season. Oregon had been 0-6 away from home.


During the November 7th meeting with R.D. James, the nominee for Assistant Secretary of the Army – Civil Works, Senator John Hoeven urged the nominee, if confirmed, to help advance comprehensive flood protection in North Dakota.
The senator introduced James to the flood study in the Minot region and the work to secure flood protection for the Red River Valley, stressing the importance of these efforts to the long-term security and well-being of families and businesses in the regions.
"Continuing to secure support from officials and nominees is an important part of keeping this process moving forward and preventing delays for our flood protection efforts," Hoeven said.
Hoeven is working as a member of the Senate Energy and Water Appropriations Committee to support regional flood protection efforts in North Dakota.
Posted on November 9, 2017 with tags flood protection, Minot, North Dakota, Senator John Hoeven, USACE.


I came across this little gem a while ago and just had to share, though it might be a bit of a stretch to classify the book as prairie history. Guy Vanderhaeghe grew up in Esterhazy, Saskatchewan and lives in Saskatoon. Last summer, I noted that he hosted the book launch for Bill Waiser. Apparently, they have been good friends for 30 years.
I know from experience that writing can be very autobiographical — what else does one draw from but their own life experiences? I guess that's why there are very few twenty year old writers. I suspect at least some of the ten short stories in this book are in the autobiographical category.
I am a huge fan of John Steinbeck, but after reading this book, I am of the opinion that Guy Vanderhaeghe's writing rivals anything Steinbeck has ever published. As it says on the book cover, Vanderhaeghe describes the human condition superbly — at its best, and its worst. The stories, the text and the dialogue are as real as anything I have ever read and at times I was laughing out loud as I savoured the ten short stories.
I would highly recommend this book to anyone with a sense of humour. It is full of prairie people.


text:
Day: Mar 11, 2020
FATHER SUGAR RAY TO SPEND €30,000 ON NEW VANITY ALBUM – GIVING TO CHARITY GOES OUT THE WINDOW.
133 Comments on FATHER SUGAR RAY TO SPEND €30,000 ON NEW VANITY ALBUM – GIVING TO CHARITY GOES OUT THE WINDOW.
Eugene Masterson Irish Independent
Fr Ray Kelly with Pro dancer Kylee Vincent pictured during the live show of Dancing with the stars.
Father Ray Kelly is dancing with joy as he has made an estimated €30,000 from Dancing with the Stars.
And the 66-year-old priest has revealed exactly what he's going to do with his windfall from the hit RTE show.
"I'm releasing an album shortly and I owe the record producer in London almost €30,000 for the album I recorded," he told the Sunday World.
"I am paying him back by degrees, because no record company came behind me to do this album this time around. So I have to pay for it myself. Most of it will go towards that."
Fr Ray Kelly with Pro dancer Kylee Vincent pictured after they were voted out during the live show of Dancing with the stars.
Contestants on the TV ballroom dancing show are reportedly paid €3,000 for each episode they take part in and last night saw Fr Ray notch up his 10th appearance.
"The album, which will be my third one to release, may be called Hallelujah Day or Father Of All Ages," says Fr Ray, who first shot to fame when a video of him singing Leonard Cohen's Hallelujah back in 2014 became a YouTube hit.
4Fr Ray Kelly with Pro dancer Kylee Vincent pictured during the live show of Dancing with the stars. Photo Credit: Kyran O'Brien Photography/kobpix
"The songs are all recorded, I'm waiting for the final tweaking. I have a colleague in Vienna putting the rest of it together and hopefully it will be out in the next month or so."
Fr Ray, who is based in Tyrrellspass, Co Westmeath, later shot to TV fame in 2018 when he made the semi-finals of Britain's Got Talent, bowling over Simon Cowell and the other judges with his version of R.E.M's Everybody Hurts.
4Double act: Fr Ray Kelly performing on 'Dancing with the Stars' with his partner Kylee Vincent. Photo: Kyran O'Brien
He was approached by producers with DWTS to take part on the RTE show later the same year but he fractured his foot as a concert, which put him out of action.
"Then they came back again and asked would I consider it this time around and I said `yeah, let me think about it'," he explains.
"In hindsight looking back on it, when you sign up for something like that you don't really realise the amount of work involved or the intensity of the training, because it can be 12 or 14 hours a week, but in fairness we put in an awful lot more than that
"I'm still getting on doing my parish work. Life is normal for me. I never asked for this…even the word celebrity, dancing with the stars, stars and celebrity status doesn't really sit comfortably enough with me because I just see myself as an ordinary priest getting on with life and doing the best I can."
Sugar Ray Kelly gets more embarrassing and cringeworthy every time he opens his mouth.
Why is a 66 year old man so attention seeking, so approval craving?
Ok, he can sing. But he is no Frank Sinatra.
He now admits that he has spent more than two days a week out of his parish and other priests have to celebrate his Masses.
Ray is so needy and attention seeking it is embarrassing.
DOZENS OF US CATHOLIC PRIEST ABUSERS IN MINISTRY ELSEWHERE.
82 Comments on DOZENS OF US CATHOLIC PRIEST ABUSERS IN MINISTRY ELSEWHERE.
The Catholic Church allowed more than 50 U.S.-based clergy to move abroad after facing credible accusations of sexual abuse. Some continued to work with children.
by Katie Zavadski, Topher Sanders,
ProPublica, and Nicole Hensley, Houston Chronicle March 6
The Rev. Jeffrey David Newell blesses children at Our Lady of the Incarnation in Tijuana, Mexico, in November 2019. Newell has continued to serve as a priest despite being accused of sexually abusing a teenager in the U.S.(Elizabeth Conley/Houston Chronicle)
ProPublica is a nonprofit newsroom that investigates abuses of power. Sign up to receive our biggest stories as soon as they're published.
This story is co-published with the Houston Chronicle.
The Rev. Jose Antonio Pinal, a young priest from Mexico, arrived at his first parish in rural Northern California in 1980, fresh out of seminary. The priest befriended the Torres family, helping the parents, also immigrants from Mexico, to fill out an application for food stamps. Pinal became an occasional dinner guest and took the children to theme parks and on road trips along the Pacific coast. He encouraged 15-year-old Ricardo Torres to become an altar boy.
But in the priest's quarters at Sacred Heart Catholic Church in the small city of Gridley, Torres said, Pinal, then 30, gave him alcohol, showed him movies with sex and nudity, and groped and raped him. The teenager told another priest in 1989 and the family was assured by lawyers for the diocese that Pinal would not be allowed around children, Torres said.
Thirty years later, in the spring of 2019, the Diocese of Sacramento put Pinal's name on its list of credibly accused priests. The list had five allegations of sexual abuse against Pinal dating to the late 1980s.
Pinal had "fled to Mexico," according to the list, and the diocese had prohibited him from performing priestly work in public in the 20 counties that make up the diocese. But an investigation by ProPublica and the Houston Chronicle shows the Catholic Church allowed or aided dozens of priests — including Pinal — to serve abroad as priests after being credibly accused of abuse in the United States.
ProPublica and the Chronicle analyzed lists published by 52 U.S. dioceses — encompassing the top 30 in terms of the number of credibly accused living clergy and those located in states along the U.S.-Mexico border. Reporters found 51 clergy who after allegations of abuse in the U.S. were able to work as priests or religious brothers in a host of countries, from Ireland to Nigeria to the Philippines. At least 40 had worked in U.S. states along the southern border, including 11 in Texas. No country was a more common destination than Mexico, where at least 21 credibly accused clergy found refuge.
Using social media, a reporter easily located Pinal, who lives in Cuernavaca, about 55 miles south of Mexico City.
In an interview at his home and in a subsequent series of email exchanges, Pinal repeatedly denied sexually abusing Torres or that he "fled" California. But in some of the emails, he referred to what "happened" between him and Torres, and in an email sent Wednesday night, about a trip he took with Torres, Pinal said, "It was screwed up, but whatever happened was consensual."
Just months after the allegations in California, Pinal resumed priestly work, ministering in indigenous villages in and around Tepoztlán, a small town near Mexico City known for archaeological sites, and he went on to serve for decades in parishes in the Diocese of Cuernavaca.
Now 68, he ministers from his home, where he has letters showing the church in Sacramento kept him on the payroll as it helped him find a new assignment. Pinal enjoyed a warm correspondence with the then-Sacramento bishop and officials in charge of Hispanic ministry, who in the months after the allegations advised him to work in Mexico for a "long period (5-6 years)" before returning to the U.S. Letters from the bishop were signed "con cariño," or with affection.
"This was a grave failure of judgment and a betrayal of trust," the current Sacramento bishop, Jaime Soto, said after correspondence between his predecessor and Pinal was released to Torres' attorney through litigation. "The safety of children is our highest priority. In 1989, those in leadership failed to do so. I must own and atone for this."
After being contacted by reporters, the Diocese of Sacramento acknowledged that the characterization that Pinal "fled" to Mexico is incorrect, and in recent days, the diocese revised the list to "more accurately reflect the circumstances of his 1989 departure."
Since 2018, many Catholic dioceses and religious orders in the U.S., including Sacramento, have released lists of clergy deemed credibly accused of abusing children. Others updated and expanded lists they had already made public. For the church, the wave of disclosures has been a belated reckoning with the extent of the sexual abuse crisis that was exposed two decades ago.
But the 178 lists made public as of January and compiled into a searchable database by ProPublica revealed a web of incomplete and often inconsistent information.
Often the lists didn't specify clergy's current status and location. And while dioceses frequently claim to know nothing about a priest's whereabouts, reporters with ProPublica and the Chronicle found them on church websites, in religious publications and on social media. Church leaders often failed to report allegations to police, to pursue permanent restrictions within the church, or to heed or offer warnings about priests facing allegations. In at least four cases, church leaders facilitated priests' moving abroad.
The omissions, inconsistencies and other shortcomings undercut the church's professed desire to repair its relationship with millions of disaffected Catholics, said Anthony M. DeMarco, a California lawyer who has handled hundreds of child sex abuse cases. "Every bit of hedging that they do to protect a pedophile just undermines completely any level of trust they're trying to build," he said.
Pinal keeps stacks of photo albums and papers documenting the nearly 10 years he spent at the Diocese of Sacramento, which covers the capital city and large swaths of rural Northern California.
"It was a nice time," Pinal recalled wistfully.
In one letter Pinal has saved, Bishop Francis Quinn told Pinal he "will be of whatever assistance is necessary in supporting your efforts to seek a new diocese." The letter was written in 1990, the year after Pinal's alleged abuse was reported to the church.
When the bishop for Cuernavaca offered Pinal a permanent appointment, Quinn (who died last year) was enthusiastic. "I am happy to hear that you have found such a fulfilling ministry," the bishop wrote.
The prior year, Pinal had assailed his accuser in a letter to officials in charge of Hispanic ministry, Torres bore responsibility for what happened. "With this boy, what happened happened because he brought it about; and, if I am worried about his recovery, it's not because I feel at fault for his trauma but because of the friendship I had with his family," Pinal wrote.
Pinal said Torres was reluctant to talk to clergy about this because he was at fault. "If he refuses to talk with any priest, I don't think it's because he is rejecting me but because he knows that he is not innocent of the situation he wants to blame me for completely. His only advantage over me is that when this happened, he was a minor; so, legally, I am screwed. Because of this I had to leave the diocese and the United States, as you mentioned, for a long period of time (5-6 years)."
Ricardo Torres in Sacramento, California. Torres says he was abused by Pinal beginning at the age of 15. (Rachel Bujalski for ProPublica)
Last October, Torres filed suit against the diocese again, this time under California's new Child Victims Act, which provides a three-year window for victims of child abuse to bring lawsuits that otherwise would have been outside the statute of limitations. The lawsuit alleges, among other counts, that the diocese's negligence enabled Pinal to molest Torres and that the diocese failed to report the abuse to relevant authorities.
Torres said the church mollified his family by misleading them about the steps taken to curtail Pinal's ability to minister. "This was supposed to be the most trustworthy person," Torres said of Pinal. "He was supposed to be next to God."
"The Past Is the Past"
For decades, the Catholic Church in the United States concealed abuse by clergy, transferring priests from parish to parish, sometimes cloaking reasons for moves in code, such as "family and health reasons." The demand for Spanish-speaking clergy in the U.S. — driven by an increase of about 45 million Catholics since the 1950s, with the largest growth among Latino faithful — made it easier for priests to cross international lines, experts said, but harder to hold them accountable.
It is "all that much harder to track them when they're in another country," said Erin Gallagher, an investigator for the International Criminal Court in The Hague, who helped track down fugitive priests in the early 2000s when she was working in the San Francisco District Attorney's Office. "They are pariahs here and they can go live someplace else anonymously."
The ProPublica-Chronicle investigation found that the church's ability to track abusive priests was even more limited internationally than within U.S. borders. Because the Vatican does not dictate what bishops must disclose about accused clergy, either within the church or to the public, bishops in many countries have released even less information than those in the U.S.
No diocese in Mexico, which is home to about 90 million Catholics, has published a list of credibly accused priests, though Mexican church officials reported in January that 271 priests have been investigated in the past decade in connection with sexual abuse allegations.
An advocacy group for abuse victims in Mexico compiled a list of accused priests in 2010.
In the U.S., some offenders were laicized — stripped of the power to be a priest. But others left their dioceses and resumed priestly work in Mexico, ProPublica and the Chronicle found. Some crisscrossed the border with ease after being accused of sexual abuse, securing new posts even after being sent for treatment by the church. Others settled into parishes south of the border decades ago, delivering sermons and blessing babies as the statute of limitations for prosecution in the U.S. expired.
Search lists of U.S. Catholic clergy who have been deemed credibly accused of sexual abuse or misconduct by the church, including some who moved abroad.
Credibly Accused
Over the last year and a half, U.S. dioceses and religious orders covering most of the Catholics in the country have released lists of what they regard as "credibly accused" abusers who have served in their ranks. You can search these lists in our interactive database.
The Rev. Jose Luis Urbina is still wanted on a three-decade-old warrant issued in California, Yuba County Deputy District Attorney Shiloh Sorbello said. Urbina, after pleading guilty to sexual abuse of a child in 1989, fled the country before he could be sentenced and then served as a priest in his hometown of Navojoa, Mexico, where The Dallas Morning News tracked him down in 2005. The paper said that in a phone interview, the priest admitted his guilt. Authorities in the U.S. sought to extradite Urbina that year, but the Mexican government declined to send him back, Sorbello said. The warrant was renewed in 2019 in case Urbina tried to return to the U.S., Sorbello said.
"Murder cases usually get top billing for extradition," Sorbello said. "We don't have any resources to have people go to Mexico to locate this man. And the Mexican authorities probably don't have much incentive to do our work for us."
Urbina was removed from the priesthood by Pope Benedict XVI in 2008, according to the Diocese of Sacremento's list.
One of the most notorious cases of an accused priest moving across international borders was the Rev. Nicolas Aguilar Rivera. After abuse allegations first surfaced in 1987 in the southern Mexican city of Tehuácan, he was attacked by parishioners and then sent by church leaders to Los Angeles. Less than a year after arriving in California, he faced similar allegations, which eventually led to charges that he molested 10 boys. Church leaders confronted Aguilar before notifying police and he returned to Mexico, where he continued to abuse minors, according to lawsuits and criminal complaints filed in Mexico.
Years later, lawyers suing the Archdiocese of Los Angeles on behalf of abuse victims questioned Cardinal Norberto Rivera, then the Mexico City archbishop, about whether church leaders used code words — "family and health reasons" — to cloak the true reason for the transfers abroad. As the bishop of Tehuácan, Rivera had helped transfer Aguilar to the U.S. Aguilar needed "to attend to the problem I suspected he had, which was a health problem," the cardinal explained in a deposition. "To be specific, homosexuality."
The Archdiocese of Mexico City said Aguilar is believed to be deceased and that it is not aware of any complaints against him; the archdiocese did not respond to Rivera's statements.
Some priests served for decades in Mexico and retired or died before being named on any list.
The Archdiocese of San Antonio included the Rev. Jose Luis Contreras on its list of credibly accused priests released in 2019 — more than 30 years after he was accused of inappropriately touching a 17-year-old male patient while serving as a chaplain at a San Antonio hospital, according to the archdiocese.
Contreras was sent for treatment in 1987 and barred from working in San Antonio-area churches again, according to the list, which stated that Contreras returned to Mexico to be with his sister in Guadalajara.
But Contreras was able to work as priest in both the U.S. and Mexico after the allegation.
Robert F. Vasa, the current bishop in Santa Rosa, California, said Contreras served in parishes there between 1995 and 2000, providing the Diocese of Santa Rosa with a letter of recommendation from the Diocese of Tepic, located in the western state of Nayarit, Mexico.
Vasa said he found no indication of the Texas abuse allegation in Contreras' paperwork, copies of which he declined to share. But there was also a letter of support from a Santa Rosa priest that mentioned the five years Contreras spent in San Antonio — work history that was missing from Contreras' resume.
"Should that have been spotted?" Vasa said of the five-year gap. "Now looking back, sure."
Nothing in the file, Vasa said, reveals whether the prior bishop or his staff noticed the discrepancy.
"To spot that discrepancy would entail a prior suspicion, and unfortunately in those days they were not suspicious enough about many things," he said. Even had the bishop or his staff noticed the inconsistency, Vasa said he isn't sure it would have prevented Contreras from gaining a position in Santa Rosa.
"I can't say what would raise red flags in 1994 and what wouldn't," he said. "We're much more suspicious now."
Contreras retired shortly after celebrating the 50th anniversary of his ordination in a ceremony at a parish in Colima, a small state in western Mexico, in 2017.
After reporters sent the diocese a copy of the list and specific questions about Contreras, officials responded with a statement declining comment, citing "the distrust and danger that unfortunately prevails throughout Mexico." The Rev. Jesús Ramos Hueso, vicar general in Colima, said recently that no one in his diocese was aware of the allegations lodged against Contreras in San Antonio.
Contreras faces little risk of legal repercussions in the U.S. A reporter found no record that the allegation against him was reported to law enforcement. Regardless, prosecuting Contreras would be impossible now as the Texas statute of limitations on the allegation ran out decades ago, officials said.
Contreras, reached by phone, declined to hear the specific allegation against him and later blocked a reporter from contacting him. "I've already delivered myself to the Lord," Contreras said. "For me, the past is a blessing from God and nothing else. For me, the past is the past."
"I Wasn't a Saint"
On a balmy Sunday morning in early November in Tijuana, Mexico, worshippers at Our Lady of the Incarnation greeted one another with hugs, handshakes and smiles. The church, on the west side of Tijuana's Camino Verde neighborhood, was abuzz before Mass. Taxis lined the streets letting out customers: merchants laid out religious material as norteña music blared from speakers.
In the church courtyard, where dozens of children laughed and played, a reporter found Rev. Jeffrey David Newell, the church's pastor.
According to the credibly accused list published in 2018 by the Archdiocese of Los Angeles, Newell is "inactive" — suggesting he no longer serves as a priest. But a Google search by reporters revealed Newell's name on the Archdiocese of Tijuana's website, listing him as the pastor at Our Lady of the Incarnation.
Newell holds a chalice for the blessing of the Eucharist during Mass at Our Lady of the Incarnation, the Catholic church in Tijuana where he is the pastor. (Elizabeth Conley/Houston Chronicle)
Newell, 58, was accused nearly 30 years ago of sexually abusing a teenager in Los Angeles, according to interviews and a lawsuit filed a decade ago. (The lawsuit has since been dismissed because it wasn't filed within the statute of limitations.) The boy met Newell in 1984 when he was a lay youth minister at St. Catherine of Siena School.
The teenager said the abuse started in 1986, when he was 15, and went on for years. In 1991, he told officials in the Archdiocese of Los Angeles about the abuse and was promised Newell "would be removed from the priesthood and no longer able to sexually abuse children," according to the lawsuit.
Newell, interviewed briefly at the church in Tijuana, said he confessed to church leaders decades ago and had multiple rounds of treatment and therapy.
"It happened," he said. "I admitted it. I made a mistake."
He disputed only the age of the victim at the time of the encounters: Newell said the victim was 17, not 15.
In response to questions from ProPublica and the Chronicle, the Archdiocese of Los Angeles said Newell admitted in 1991 to the "relationship" with a 17-year-old.
"After an adult made a report of sexual misconduct against Fr. Newell in May 1991, he was sent for evaluation and treatment from May to November 1991," the Archdiocese of Los Angeles said. "He admitted to having an inappropriate relationship which began before he was ordained (when the alleged victim was 17 years old) and continued while he was priest (when the alleged victim was an adult)."
The archdiocese said Newell's status is listed as "inactive" on its list because the status descriptions are intended to pertain only to the Archdiocese of Los Angeles.
Newell said he's not the same person he was back then.
"I don't know how you were when you were 23 years old," Newell said. "I wasn't a saint; I don't know how many people are. That's my job, working with sinners of all levels, and yet people expect something of us