diff --git a/README.md b/README.md index aa33feaf..bc1f117d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ ignore: true TBD. +To work with this repo: +```gh repo fork https://github.com/bcgov/bcregistry-sre``` + ## Getting Help or Reporting an Issue To report bugs/issues/feature requests, please file an [issue](https://github.com/bcgov/bcregistry-sre/issues/). diff --git a/support/README.md b/support/README.md new file mode 100644 index 00000000..15c6317a --- /dev/null +++ b/support/README.md @@ -0,0 +1,25 @@ +# BC Registry SRE Team + +## OPs notebooks + +To use the notebooks: +- Have VSCode installed +- Have Docker or equivalent services installed +- make sure you have [Remote Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed. + +1. Open the **ops** directory in VSCode and choose to use the *devcontainer* when prompted. + +1. Setup you *port-forwarding* or service forwarding. + +1. Update **bcr-business-setup.ipynb** with the values to connect to the services. + +1. Start the server in a VSCode Terminal using ```./run.sh``` + +1. Go to the notebook index, which will look like *http://127.0.0.1:8080/?token=som-long-token* listed in the terminal window + +1. Select the notebook template that most closely aligns the task at hand. + +The notebooks, organization of them, descriptions, etc. will be done by the SRE teams as this library is expanded upon. + +**NOTE**: remember to update your python libraries from the various projects to use the latest release when working in that area. + diff --git a/support/ops/assets/.devcontainer/Dockerfile b/support/ops/assets/.devcontainer/Dockerfile new file mode 100644 index 00000000..3f7e30bf --- /dev/null +++ b/support/ops/assets/.devcontainer/Dockerfile @@ -0,0 +1,56 @@ +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +FROM python:3 + +# Avoid warnings by switching to noninteractive +ENV DEBIAN_FRONTEND=noninteractive + +# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" +# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs +# will be updated to match your local UID/GID (when using the dockerFile property). +# See https://aka.ms/vscode-remote/containers/non-root-user for details. +ARG USERNAME=notebook +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Uncomment the following COPY line and the corresponding lines in the `RUN` command if you wish to +# include your requirements in the image itself. It is suggested that you only do this if your +# requirements rarely (if ever) change. +# COPY requirements.txt /tmp/pip-tmp/ + +# Configure apt and install packages +RUN apt-get update \ + && apt-get -y install --no-install-recommends apt-utils dialog dnsutils 2>&1 \ + # + # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed + && apt-get -y install git openssh-client iproute2 procps lsb-release \ + # + # Install pylint + && pip --disable-pip-version-check --no-cache-dir install pylint \ + # + # Update Python environment based on requirements.txt + # && pip --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ + # && rm -rf /tmp/pip-tmp \ + # + # Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user. + && groupadd --gid $USER_GID $USERNAME \ + && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ + # [Optional] Add sudo support for the non-root user + && apt-get install -y sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + # + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +# Switch back to dialog for any ad-hoc use of apt-get +ENV DEBIAN_FRONTEND=dialog + +# USER $USERNAME + + diff --git a/support/ops/assets/.devcontainer/devcontainer.json b/support/ops/assets/.devcontainer/devcontainer.json new file mode 100644 index 00000000..36e6606a --- /dev/null +++ b/support/ops/assets/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.101.0/containers/python-3 +{ + "name": "Python 3", + "context": "..", + "dockerFile": "Dockerfile", + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.pylintPath": "/usr/local/bin/pylint" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python" + ] + + "appPort": [ + 3000, + 8888 + ], + "postCreateCommand": "pip install -r requirements.txt", + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip install -r requirements.txt", + // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. + // "remoteUser": "vscode" +} \ No newline at end of file diff --git a/support/ops/entities/.devcontainer/Dockerfile b/support/ops/entities/.devcontainer/Dockerfile new file mode 100644 index 00000000..3f7e30bf --- /dev/null +++ b/support/ops/entities/.devcontainer/Dockerfile @@ -0,0 +1,56 @@ +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +FROM python:3 + +# Avoid warnings by switching to noninteractive +ENV DEBIAN_FRONTEND=noninteractive + +# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" +# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs +# will be updated to match your local UID/GID (when using the dockerFile property). +# See https://aka.ms/vscode-remote/containers/non-root-user for details. +ARG USERNAME=notebook +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Uncomment the following COPY line and the corresponding lines in the `RUN` command if you wish to +# include your requirements in the image itself. It is suggested that you only do this if your +# requirements rarely (if ever) change. +# COPY requirements.txt /tmp/pip-tmp/ + +# Configure apt and install packages +RUN apt-get update \ + && apt-get -y install --no-install-recommends apt-utils dialog dnsutils 2>&1 \ + # + # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed + && apt-get -y install git openssh-client iproute2 procps lsb-release \ + # + # Install pylint + && pip --disable-pip-version-check --no-cache-dir install pylint \ + # + # Update Python environment based on requirements.txt + # && pip --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ + # && rm -rf /tmp/pip-tmp \ + # + # Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user. + && groupadd --gid $USER_GID $USERNAME \ + && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ + # [Optional] Add sudo support for the non-root user + && apt-get install -y sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + # + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +# Switch back to dialog for any ad-hoc use of apt-get +ENV DEBIAN_FRONTEND=dialog + +# USER $USERNAME + + diff --git a/support/ops/entities/.devcontainer/devcontainer.json b/support/ops/entities/.devcontainer/devcontainer.json new file mode 100644 index 00000000..36e6606a --- /dev/null +++ b/support/ops/entities/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.101.0/containers/python-3 +{ + "name": "Python 3", + "context": "..", + "dockerFile": "Dockerfile", + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.pylintPath": "/usr/local/bin/pylint" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python" + ] + + "appPort": [ + 3000, + 8888 + ], + "postCreateCommand": "pip install -r requirements.txt", + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip install -r requirements.txt", + // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. + // "remoteUser": "vscode" +} \ No newline at end of file diff --git a/support/ops/entities/NRs_Stuck_In_Draft.ipynb b/support/ops/entities/NRs_Stuck_In_Draft.ipynb new file mode 100644 index 00000000..e0500575 --- /dev/null +++ b/support/ops/entities/NRs_Stuck_In_Draft.ipynb @@ -0,0 +1,396 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# NameX NameRequest stuck in Draft, but Paid" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need to load in these libraries into our notebook in order to query, load, manipulate and view the data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import os\n", + "from datetime import tzinfo, timedelta, datetime, timezone\n", + "import psycopg2\n", + "import pandas as pd\n", + "import matplotlib\n", + "import time\n", + "# from datetime import datetime, timedelta\n", + "from dateutil.relativedelta import relativedelta\n", + "from IPython.core.display import HTML\n", + "\n", + "%load_ext sql\n", + "%config SqlMagic.displaylimit = 5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%run ./utility.ipynb" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This will create the connection to the database and prep the jupyter magic for SQL" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sbc_pay = 'postgresql://postgres:postgres@host.docker.internal:6666/pay-db';\n", + " \n", + "%sql $sbc_pay" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "namex = 'postgresql://postgres:postgres@host.docker.internal:4444/namex';\n", + " \n", + "%sql $namex" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Simplest query to run to ensure our libraries are loaded and our DB connection is working" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%%sql $sbc_pay\n", + "select now() AT TIME ZONE 'PST' as current_date" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%%sql $namex\n", + "select now() AT TIME ZONE 'PST' as current_date" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sql $namex rs_today <<\n", + "select (now() AT TIME ZONE 'PST')::date as today" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inlude_last_number_of_days=10\n", + "report_start_date=rs_today[0].today - timedelta(days=inlude_last_number_of_days)\n", + "report_start_date" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Weekly running time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# %%sql $sbc_pay payments <<\n", + "# SELECT i.business_identifier, \n", + "# i.id invoice_id, \n", + "# i.created_on,\n", + "# ir.invoice_number, \n", + "# i.invoice_status_code invoice_status, \n", + "# p.payment_status_code pay_status, \n", + "# i.total, \n", + "# i.paid, \n", + "# r.receipt_number \n", + "# FROM invoices i \n", + "# LEFT OUTER JOIN invoice_references ir \n", + "# ON ir.invoice_id = i.id \n", + "# LEFT OUTER JOIN payments p \n", + "# ON p.invoice_number = ir.invoice_number \n", + "# LEFT OUTER JOIN receipts r \n", + "# ON r.invoice_id = i.id \n", + "# WHERE \n", + "# created_on >= :report_start_date\n", + "# and payment_status_code = 'COMPLETED'\n", + "# and total <> 101.5\n", + "# ORDER BY invoice_id ASC;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# pay_frame = payments.DataFrame()\n", + "# pay_frame['nr_num']=pay_frame['business_identifier']\n", + "# pay_frame" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %%sql $namex name_requests <<\n", + "# select id,nr_num,source,request_id,request_type_cd,priority_cd,state_cd\n", + "# from requests\n", + "# where submitted_date >= :report_start_date\n", + "# and state_cd not in ('CANCELLED','PENDING_PAYMENT') ;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# nr_frame = name_requests.DataFrame()\n", + "# nr_frame" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# result = pd.merge(pay_frame, nr_frame, how='inner', on=['nr_num'])\n", + "# result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "time_stamp = time.time()\n", + "now = datetime.utcfromtimestamp(time_stamp).replace(tzinfo=timezone.utc)\n", + "local_now = now.astimezone(Pacific)\n", + "local_now.strftime(\"%Y.%m.%d.%H\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# with open('nr_draft_no_pay.'+local_now.strftime(\"%Y.%m.%d.%H\")+'.csv', 'a') as f: \n", + "# f.write('\\n\\n Name Requests\\n')\n", + "# result.to_csv(f, sep=',', encoding='utf-8', index=False) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### get payment info over the reporting period" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sql $sbc_pay paid <<\n", + "SELECT i.business_identifier, \n", + " i.id invoice_id, \n", + " i.created_on,\n", + " ir.invoice_number, \n", + " i.invoice_status_code invoice_status, \n", + " p.payment_status_code pay_status, \n", + " i.total, \n", + " i.paid, \n", + " r.receipt_number \n", + "FROM invoices i \n", + " LEFT OUTER JOIN invoice_references ir \n", + " ON ir.invoice_id = i.id \n", + " LEFT OUTER JOIN payments p \n", + " ON p.invoice_number = ir.invoice_number \n", + " LEFT OUTER JOIN receipts r \n", + " ON r.invoice_id = i.id \n", + "WHERE \n", + " created_on >= :report_start_date\n", + " and i.invoice_status_code = 'PAID'\n", + " and i.business_identifier like 'NR%'\n", + "ORDER BY invoice_id ASC;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "paid_frame = paid.DataFrame()\n", + "paid_frame['nr_num']=paid_frame['business_identifier']\n", + "paid_frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## get name request status overthe reporting period" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sql $namex pending <<\n", + "select id,nr_num,source,request_id,request_type_cd,priority_cd,state_cd \n", + "from requests \n", + "where submitted_date >= :report_start_date\n", + "and state_cd in ('PENDING_PAYMENT');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pending_frame = pending.DataFrame()\n", + "pending_frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## natural join on Name Requests in DRAFT status that have been Paid" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "paid_for_pending_result = pd.merge(paid_frame, pending_frame, how='inner', on=['nr_num'])\n", + "paid_for_pending_result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with open('nr_paid_and_pending.'+local_now.strftime(\"%Y.%m.%d.%H\")+'.csv', 'a') as f: \n", + " f.write('\\n\\n Name Requests\\n')\n", + " paid_for_pending_result.to_csv(f, sep=',', encoding='utf-8', index=False) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.5" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/support/ops/entities/bcr-bcomp-alteration-template.ipynb b/support/ops/entities/bcr-bcomp-alteration-template.ipynb new file mode 100644 index 00000000..0d997043 --- /dev/null +++ b/support/ops/entities/bcr-bcomp-alteration-template.ipynb @@ -0,0 +1,463 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Alteration Filing\n", + "## Follow these steps to complete the alteration filing for a corporation in lear\n", + "\n", + "**Note:** Before running these steps this corporation will need to be loaded into lear by using the data_loader located in *lear/lear-db/test_data/data_loader.py*." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup the environment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%run ./bcr-business-setup.ipynb" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set the business identifier\n", + "**eg. BC1234567**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "identifier = ''" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Verify business info is correct" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "business = legal_api.models.Business.find_by_identifier(identifier)\n", + "business.id, business.identifier, business.legal_name, business.legal_type" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Affiliate business to business account" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Authenticate" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "token = AccountService.get_bearer_token()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get account number" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# uncomment for account name or account id\n", + "\n", + "# bcol_account_id=\n", + "# url = f'orgs?bcolAccountId={bcol_account_id}'\n", + "\n", + "# account_name=''\n", + "# url = f'orgs?name={account_name.replace(\" \", \"%20\")}'\n", + "\n", + "account_call = requests.get(\n", + " f'{os.getenv(\"AUTH_URL\")}/{url}',\n", + " headers={'Content-Type': 'application/json', 'Authorization': f'Bearer {token}'}\n", + ")\n", + "account_call.status_code, account_call.json()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# double check that ['orgs'][0]['id'] is the right id -> may need to change this\n", + "account_number = account_call.json()['orgs'][0]['id']\n", + "account_number" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Affiliate using the account number" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "affiliation = AccountService.create_affiliation(\n", + " account=account_number,\n", + " business_registration=business.identifier,\n", + " business_name=business.legal_name,\n", + " corp_type_code=Business.LegalTypes.BCOMP.value\n", + ")\n", + "affiliation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fill out the filing json for the alteration\n", + "*note: uncomment code as needed*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Base filing json for alteration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "filing_json = {\n", + " 'filing': {\n", + " 'header': {\n", + " 'name': 'alteration',\n", + " 'certifiedBy': '',\n", + " 'date': '', # today\n", + " 'effectiveDate': '', # 1970-01-01T00:00:00+00:00\n", + "\n", + " # 'bcolAccountNumber': '', # optional - depends on payment\n", + " # 'datNumber': '', # optional - depends on payment\n", + " # 'routingSlipNumber': '', # optional - depends on payment\n", + " # 'folioNumber': '' # optional\n", + " },\n", + " 'business': {\n", + " 'identifier': business.identifier,\n", + " 'legalName': business.legal_name,\n", + " 'legalType': business.legal_type\n", + " },\n", + " 'alteration': {\n", + " 'provisionsRemoved': ,\n", + " 'business': {\n", + " 'identifier': business.identifier,\n", + " # need to uncomment one of below\n", + " # 'legalType': Business.LegalTypes.BCOMP.value\n", + " # 'legalType': Business.LegalTypes.COMP.value\n", + " },\n", + " 'contactPoint': {\n", + " 'email': '',\n", + " # 'phone': '' # optional\n", + " }\n", + " }\n", + " }\n", + "}\n", + "filing_json" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add on name request if required" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Get nr info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nr = NameXService.query_nr_number('') # NR 1234567\n", + "nr.status_code, nr.json()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Fill in nr info" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "name_request = {\n", + " 'legalName': '',\n", + " 'nrNumber': '',\n", + " 'legalType': Business.LegalTypes.BCOMP.value\n", + "}\n", + "# filing_json['filing']['alteration']['nameRequest'] = name_request\n", + "filing_json" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add on name translations if required" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# new_name_translations = [, ]\n", + "# modified_translations = [\n", + "# {\n", + "# 'oldValue': ,\n", + "# 'newValue': \n", + "# }\n", + "# ]\n", + "# ceased_translations = [, ]\n", + "\n", + "name_translations = {\n", + " # 'new': new_name_translations,\n", + " # 'modified': modified_translations,\n", + " # 'ceased': ceased_translations\n", + "}\n", + "\n", + "# filing_json['filing']['alteration']['nameTranslations'] = name_translations\n", + "filing_json" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add on alter share structure if required\n", + "\n", + "#### Make base json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "share_structure = {\n", + " 'resolutionDates': ['', ],\n", + " 'shareClasses': [] # keep this empty\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create share classes and series json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_share_class = {\n", + " 'name': '',\n", + " 'priority': ,\n", + " 'hasMaximumShares': ,\n", + " 'maxNumberOfShares': ,\n", + " 'hasParValue': ,\n", + " 'parValue': ,\n", + " 'hasRightsOrRestrictions': ,\n", + " 'currency': '',\n", + " 'series': [] # keep this empty\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*repeat below for each series in the above class*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_share_series = {\n", + " 'name': ,\n", + " 'priority': ,\n", + " 'hasMaximumShares': ,\n", + " 'maxNumberOfShares': ,\n", + " 'hasRightsOrRestrictions': \n", + "}\n", + "new_share_class['series'].append(new_share_series)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*do not execute until all needed series were added to the new class*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "share_structure['shareClasses'].append(new_share_class)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*only execute after all classes were added (repeat above steps as necessary)* " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# filing_json['filing']['alteration']['shareStructure'] = share_structure\n", + "filing_json" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Save the alteration filing to lear" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Post to legal_api" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "r = requests.post(\n", + " f'{os.getenv(\"LEGAL_URL\")}/{business.identifier}/filings',\n", + " json=filing_json,\n", + " headers={'Content-Type': 'application/json', 'Authorization': f'Bearer {token}'}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Verify filing was successful" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "r.status_code, r.json()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "alteration = Filing.get_filings_by_type(business.id, 'alteration')\n", + "alteration.filing_date, alteration.effective_date, alteration.business_id, alteration.id, alteration.status, alteration.source, alteration.filing_json" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.2 64-bit", + "language": "python", + "name": "python38264bit13fc019d3793417290c78907530d4746" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2-final" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/support/ops/entities/bcr-business-setup.ipynb b/support/ops/entities/bcr-business-setup.ipynb new file mode 100644 index 00000000..ff9c66b1 --- /dev/null +++ b/support/ops/entities/bcr-business-setup.ipynb @@ -0,0 +1,145 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %%capture\n", + "# %env DATABASE_USERNAME=postgres\n", + "# %env DATABASE_PASSWORD=\n", + "# %env DATABASE_NAME=legal_db\n", + "# %env DATABASE_HOST=host.docker.internal\n", + "# %env DATABASE_PORT=5432\n", + "# %env SECRET_KEY=ops\n", + "# %env LEGAL_URL=https://legal-api-dev.pathfinder.gov.bc.ca/api/v1/businesses\n", + "# %env ACCOUNT_SVC_AUTH_URL=https://sso-dev.pathfinder.gov.bc.ca/auth/realms/fcf0kpqr/protocol/openid-connect/token\n", + "# %env ACCOUNT_SVC_CLIENT_ID=\n", + "# %env ACCOUNT_SVC_CLIENT_SECRET=\n", + "# %env NAMEX_AUTH_SVC_URL=https://sso-dev.pathfinder.gov.bc.ca/auth/realms/sbc/protocol/openid-connect/token\n", + "# %env NAMEX_SERVICE_CLIENT_USERNAME=\n", + "# %env NAMEX_SERVICE_CLIENT_SECRET=\n", + "# %env NAMEX_SVC_URL=https://namex-dev.pathfinder.gov.bc.ca/api/v1/\n", + "# %env AUTH_URL=https://auth-api-dev.pathfinder.gov.bc.ca/api/v1\n", + "# %env ACCOUNT_SVC_ENTITY_URL=https://auth-api-dev.pathfinder.gov.bc.ca/api/v1/entities\n", + "# %env ACCOUNT_SVC_AFFILIATE_URL=https://auth-api-dev.pathfinder.gov.bc.ca/api/v1/orgs/{account_id}/affiliations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "import os\n", + "import flask\n", + "import sqlalchemy\n", + "import psycopg2\n", + "import simplejson\n", + "import pandas as pd\n", + "import matplotlib\n", + "import legal_api\n", + "import requests\n", + "from legal_api.models import Business\n", + "from legal_api.services.namex import NameXService\n", + "from legal_api.services.bootstrap import AccountService\n", + "from IPython.core.display import HTML\n", + "%load_ext sql\n", + "%config SqlMagic.displaylimit = 5\n", + "%config Application.log_level=\"ERROR\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import flask\n", + "APP_CONFIG = legal_api.config.get_named_config(os.getenv('DEPLOYMENT_ENV', 'production'))\n", + "FLASK_APP = flask.Flask(__name__)\n", + "FLASK_APP.config.from_object(APP_CONFIG)\n", + "legal_api.db.init_app(FLASK_APP)\n", + "FLASK_APP.app_context().push()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def stop_on_false(test: bool, test_name: str):\n", + " # this will bail out of the execution if called by papermill\n", + " # failure_condition=True\n", + " # assert not test, test_name\n", + " assert test, test_name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def stop_on_true(test: bool, test_name: str):\n", + " # this will bail out of the execution if called by papermill\n", + " # failure_condition=True\n", + " assert not test, test_name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from legal_api.models import Filing, db\n", + "from sqlalchemy import or_\n", + "def get_latest_correction_filing(business_id: str) -> Filing:\n", + " filing_type='correction'\n", + " expr = Filing._filing_json[('filing', filing_type)]\n", + " max_filing = db.session.query(db.func.max(Filing._filing_date).label('last_filing_date')).\\\n", + " filter(Filing.business_id == business_id).\\\n", + " filter(or_(Filing._filing_type == filing_type,\n", + " expr.label('legal_filing_type').isnot(None))).\\\n", + " subquery()\n", + " filings = Filing.query.join(max_filing, Filing._filing_date == max_filing.c.last_filing_date). \\\n", + " filter(Filing.business_id == business_id). \\\n", + " order_by(Filing.id.desc())\n", + "\n", + " return filings.first()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/support/ops/entities/img/legal.png b/support/ops/entities/img/legal.png new file mode 100644 index 00000000..f0c5802c Binary files /dev/null and b/support/ops/entities/img/legal.png differ diff --git a/support/ops/entities/names-duplicates.ipynb b/support/ops/entities/names-duplicates.ipynb new file mode 100644 index 00000000..b6fbe2ae --- /dev/null +++ b/support/ops/entities/names-duplicates.ipynb @@ -0,0 +1,357 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# NameX Payments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need to load in these libraries into our notebook in order to query, load, manipulate and view the data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import os\n", + "from datetime import tzinfo, timedelta, datetime, timezone\n", + "import psycopg2\n", + "import pandas as pd\n", + "import matplotlib\n", + "import time\n", + "# from datetime import datetime, timedelta\n", + "from dateutil.relativedelta import relativedelta\n", + "from IPython.core.display import HTML\n", + "\n", + "%load_ext sql\n", + "%config SqlMagic.displaylimit = 5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%run ./utility.ipynb" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This will create the connection to the database and prep the jupyter magic for SQL" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sbc_pay = 'postgresql://postgres:postgres@docker.for.mac.localhost:6666/pay-db';\n", + " \n", + "%sql $sbc_pay" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "namex = 'postgresql://postgres:postgres@docker.for.mac.localhost:4444/namex';\n", + " \n", + "%sql $namex" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Simplest query to run to ensure our libraries are loaded and our DB connection is working" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%%sql $sbc_pay\n", + "select now() AT TIME ZONE 'PST' as current_date" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%%sql $namex\n", + "select now() AT TIME ZONE 'PST' as current_date" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sql $namex rs_today <<\n", + "select (now() AT TIME ZONE 'PST')::date as today" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set the number of days we want the report to be run over." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inlude_last_number_of_days=2\n", + "report_start_date=rs_today[0].today - timedelta(days=inlude_last_number_of_days)\n", + "\n", + "# inlude_last_number_of_hours=24\n", + "# report_start_date=rs_today[0].today - timedelta(hours=inlude_last_number_of_hours)\n", + "\n", + "\n", + "# report_start_date = '2020-12-01'\n", + "report_start_date" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## get all payments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%%sql $sbc_pay paid <<\n", + "SELECT i.business_identifier, \n", + " i.id invoice_id, \n", + " i.created_on,\n", + " ir.invoice_number, \n", + " i.invoice_status_code invoice_status, \n", + " p.payment_status_code pay_status, \n", + " i.total, \n", + " i.paid, \n", + " r.receipt_number \n", + "FROM invoices i \n", + " LEFT OUTER JOIN invoice_references ir \n", + " ON ir.invoice_id = i.id \n", + " LEFT OUTER JOIN payments p \n", + " ON p.invoice_number = ir.invoice_number \n", + " LEFT OUTER JOIN receipts r \n", + " ON r.invoice_id = i.id \n", + "WHERE \n", + " created_on >= :report_start_date\n", + " and i.invoice_status_code = 'PAID'\n", + " and i.business_identifier like 'NR%'\n", + " and i.paid <> 101.5\n", + "ORDER BY invoice_id ASC;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "paid_frame = paid.DataFrame()\n", + "paid_frame['nr_num']=paid_frame['business_identifier']\n", + "paid_frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## get all duplicate names" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sql $namex name_requests <<\n", + "select distinct\n", + "r.id, r.nr_num, r.priority_cd as pri, r.state_cd as nr_state,r.submitted_date,r.source,\n", + "n.name,\n", + "a.first_name||' '||a.last_name as name, a.phone_number, a.email_address\n", + "from requests r, names n, applicants a\n", + "where r.id = n.nr_id\n", + "and r.id = a.nr_id\n", + "and r.submitted_date::date >= :report_start_date\n", + "and\n", + "n.choice=1\n", + "and\n", + "n.name in (\n", + "\n", + "select \n", + "n.name\n", + "from requests r, names n\n", + "where r.id = n.nr_id\n", + "and\n", + "r.submitted_date::date >= :report_start_date\n", + "-- and r.state_cd in ('DRAFT','HOLD','PENDING_PAYMENT','CANCELLED')\n", + "-- and r.state_cd in ('DRAFT','HOLD','PENDING_PAYMENT')\n", + "--and n.choice=1\n", + "group by n.name\n", + "having count(n.name) > 1\n", + ")\n", + "order by n.name\n", + ";" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nr_frame = name_requests.DataFrame()\n", + "nr_frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Merge the Duplicate Names with Payment information" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = pd.merge(nr_frame, paid_frame, how='left', on=['nr_num'])\n", + "result=result.drop(['id','business_identifier','created_on','invoice_number','total','receipt_number'], axis=1)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "time_stamp = time.time()\n", + "now = datetime.utcfromtimestamp(time_stamp).replace(tzinfo=timezone.utc)\n", + "local_now = now.astimezone(Pacific)\n", + "local_now.strftime(\"%Y.%m.%d.%H\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write the results to a CSV file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with open('nr_duplicates_with_pay_info.'+local_now.strftime(\"%Y.%m.%d.%H\")+'.csv', 'a') as f: \n", + " f.write('\\n\\n Name Requests\\n')\n", + " result.to_csv(f, sep=',', encoding='utf-8', index=False) \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.5" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/support/ops/entities/requirements.txt b/support/ops/entities/requirements.txt new file mode 100644 index 00000000..56a5f0bf --- /dev/null +++ b/support/ops/entities/requirements.txt @@ -0,0 +1,17 @@ +jupyter +requests +d6tstack +d6tstack[psql] +d6tstack[xls] +pandas +sqlalchemy +psycopg2 +simplejson +matplotlib +ipython-sql +tlaplus_jupyter +ipytest +pytest +git+https://github.com/bcgov/business-schemas.git#egg=registry_schemas +git+https://github.com/bcgov/lear.git#egg=legal_api&subdirectory=legal-api + diff --git a/support/ops/entities/run.sh b/support/ops/entities/run.sh new file mode 100755 index 00000000..6f6426ef --- /dev/null +++ b/support/ops/entities/run.sh @@ -0,0 +1,2 @@ +#! /bin/sh +jupyter notebook --ip=0.0.0.0 --port=8080 --allow-root diff --git a/support/ops/entities/save_to_markdown.sh b/support/ops/entities/save_to_markdown.sh new file mode 100755 index 00000000..e69de29b diff --git a/support/ops/entities/utility.ipynb b/support/ops/entities/utility.ipynb new file mode 100644 index 00000000..5523a4e0 --- /dev/null +++ b/support/ops/entities/utility.ipynb @@ -0,0 +1,217 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "14dd73bd", + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import tzinfo, timedelta, datetime, timezone" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55f99dd4", + "metadata": {}, + "outputs": [], + "source": [ + "ZERO = timedelta(0)\n", + "HOUR = timedelta(hours=1)\n", + "SECOND = timedelta(seconds=1)\n", + "\n", + "# A class capturing the platform's idea of local time.\n", + "# (May result in wrong values on historical times in\n", + "# timezones where UTC offset and/or the DST rules had\n", + "# changed in the past.)\n", + "import time as _time\n", + "\n", + "STDOFFSET = timedelta(seconds = -_time.timezone)\n", + "if _time.daylight:\n", + " DSTOFFSET = timedelta(seconds = -_time.altzone)\n", + "else:\n", + " DSTOFFSET = STDOFFSET\n", + "\n", + "DSTDIFF = DSTOFFSET - STDOFFSET\n", + "\n", + "class LocalTimezone(tzinfo):\n", + "\n", + " def fromutc(self, dt):\n", + " assert dt.tzinfo is self\n", + " stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND\n", + " args = _time.localtime(stamp)[:6]\n", + " dst_diff = DSTDIFF // SECOND\n", + " # Detect fold\n", + " fold = (args == _time.localtime(stamp - dst_diff))\n", + " return datetime(*args, microsecond=dt.microsecond,\n", + " tzinfo=self, fold=fold)\n", + "\n", + " def utcoffset(self, dt):\n", + " if self._isdst(dt):\n", + " return DSTOFFSET\n", + " else:\n", + " return STDOFFSET\n", + "\n", + " def dst(self, dt):\n", + " if self._isdst(dt):\n", + " return DSTDIFF\n", + " else:\n", + " return ZERO\n", + "\n", + " def tzname(self, dt):\n", + " return _time.tzname[self._isdst(dt)]\n", + "\n", + " def _isdst(self, dt):\n", + " tt = (dt.year, dt.month, dt.day,\n", + " dt.hour, dt.minute, dt.second,\n", + " dt.weekday(), 0, 0)\n", + " stamp = _time.mktime(tt)\n", + " tt = _time.localtime(stamp)\n", + " return tt.tm_isdst > 0\n", + "\n", + "Local = LocalTimezone()\n", + "\n", + "\n", + "# A complete implementation of current DST rules for major US time zones.\n", + "\n", + "def first_sunday_on_or_after(dt):\n", + " days_to_go = 6 - dt.weekday()\n", + " if days_to_go:\n", + " dt += timedelta(days_to_go)\n", + " return dt\n", + "\n", + "\n", + "# US DST Rules\n", + "#\n", + "# This is a simplified (i.e., wrong for a few cases) set of rules for US\n", + "# DST start and end times. For a complete and up-to-date set of DST rules\n", + "# and timezone definitions, visit the Olson Database (or try pytz):\n", + "# http://www.twinsun.com/tz/tz-link.htm\n", + "# http://sourceforge.net/projects/pytz/ (might not be up-to-date)\n", + "#\n", + "# In the US, since 2007, DST starts at 2am (standard time) on the second\n", + "# Sunday in March, which is the first Sunday on or after Mar 8.\n", + "DSTSTART_2007 = datetime(1, 3, 8, 2)\n", + "# and ends at 2am (DST time) on the first Sunday of Nov.\n", + "DSTEND_2007 = datetime(1, 11, 1, 2)\n", + "# From 1987 to 2006, DST used to start at 2am (standard time) on the first\n", + "# Sunday in April and to end at 2am (DST time) on the last\n", + "# Sunday of October, which is the first Sunday on or after Oct 25.\n", + "DSTSTART_1987_2006 = datetime(1, 4, 1, 2)\n", + "DSTEND_1987_2006 = datetime(1, 10, 25, 2)\n", + "# From 1967 to 1986, DST used to start at 2am (standard time) on the last\n", + "# Sunday in April (the one on or after April 24) and to end at 2am (DST time)\n", + "# on the last Sunday of October, which is the first Sunday\n", + "# on or after Oct 25.\n", + "DSTSTART_1967_1986 = datetime(1, 4, 24, 2)\n", + "DSTEND_1967_1986 = DSTEND_1987_2006\n", + "\n", + "def us_dst_range(year):\n", + " # Find start and end times for US DST. For years before 1967, return\n", + " # start = end for no DST.\n", + " if 2006 < year:\n", + " dststart, dstend = DSTSTART_2007, DSTEND_2007\n", + " elif 1986 < year < 2007:\n", + " dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006\n", + " elif 1966 < year < 1987:\n", + " dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986\n", + " else:\n", + " return (datetime(year, 1, 1), ) * 2\n", + "\n", + " start = first_sunday_on_or_after(dststart.replace(year=year))\n", + " end = first_sunday_on_or_after(dstend.replace(year=year))\n", + " return start, end\n", + "\n", + "\n", + "class USTimeZone(tzinfo):\n", + "\n", + " def __init__(self, hours, reprname, stdname, dstname):\n", + " self.stdoffset = timedelta(hours=hours)\n", + " self.reprname = reprname\n", + " self.stdname = stdname\n", + " self.dstname = dstname\n", + "\n", + " def __repr__(self):\n", + " return self.reprname\n", + "\n", + " def tzname(self, dt):\n", + " if self.dst(dt):\n", + " return self.dstname\n", + " else:\n", + " return self.stdname\n", + "\n", + " def utcoffset(self, dt):\n", + " return self.stdoffset + self.dst(dt)\n", + "\n", + " def dst(self, dt):\n", + " if dt is None or dt.tzinfo is None:\n", + " # An exception may be sensible here, in one or both cases.\n", + " # It depends on how you want to treat them. The default\n", + " # fromutc() implementation (called by the default astimezone()\n", + " # implementation) passes a datetime with dt.tzinfo is self.\n", + " return ZERO\n", + " assert dt.tzinfo is self\n", + " start, end = us_dst_range(dt.year)\n", + " # Can't compare naive to aware objects, so strip the timezone from\n", + " # dt first.\n", + " dt = dt.replace(tzinfo=None)\n", + " if start + HOUR <= dt < end - HOUR:\n", + " # DST is in effect.\n", + " return HOUR\n", + " if end - HOUR <= dt < end:\n", + " # Fold (an ambiguous hour): use dt.fold to disambiguate.\n", + " return ZERO if dt.fold else HOUR\n", + " if start <= dt < start + HOUR:\n", + " # Gap (a non-existent hour): reverse the fold rule.\n", + " return HOUR if dt.fold else ZERO\n", + " # DST is off.\n", + " return ZERO\n", + "\n", + " def fromutc(self, dt):\n", + " assert dt.tzinfo is self\n", + " start, end = us_dst_range(dt.year)\n", + " start = start.replace(tzinfo=self)\n", + " end = end.replace(tzinfo=self)\n", + " std_time = dt + self.stdoffset\n", + " dst_time = std_time + HOUR\n", + " if end <= dst_time < end + HOUR:\n", + " # Repeated hour\n", + " return std_time.replace(fold=1)\n", + " if std_time < start or dst_time >= end:\n", + " # Standard time\n", + " return std_time\n", + " if start <= std_time < end - HOUR:\n", + " # Daylight saving time\n", + " return dst_time\n", + "\n", + "\n", + "Eastern = USTimeZone(-5, \"Eastern\", \"EST\", \"EDT\")\n", + "Central = USTimeZone(-6, \"Central\", \"CST\", \"CDT\")\n", + "Mountain = USTimeZone(-7, \"Mountain\", \"MST\", \"MDT\")\n", + "Pacific = USTimeZone(-8, \"Pacific\", \"PST\", \"PDT\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/support/ops/relationships/.devcontainer/Dockerfile b/support/ops/relationships/.devcontainer/Dockerfile new file mode 100644 index 00000000..3f7e30bf --- /dev/null +++ b/support/ops/relationships/.devcontainer/Dockerfile @@ -0,0 +1,56 @@ +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +FROM python:3 + +# Avoid warnings by switching to noninteractive +ENV DEBIAN_FRONTEND=noninteractive + +# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" +# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs +# will be updated to match your local UID/GID (when using the dockerFile property). +# See https://aka.ms/vscode-remote/containers/non-root-user for details. +ARG USERNAME=notebook +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Uncomment the following COPY line and the corresponding lines in the `RUN` command if you wish to +# include your requirements in the image itself. It is suggested that you only do this if your +# requirements rarely (if ever) change. +# COPY requirements.txt /tmp/pip-tmp/ + +# Configure apt and install packages +RUN apt-get update \ + && apt-get -y install --no-install-recommends apt-utils dialog dnsutils 2>&1 \ + # + # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed + && apt-get -y install git openssh-client iproute2 procps lsb-release \ + # + # Install pylint + && pip --disable-pip-version-check --no-cache-dir install pylint \ + # + # Update Python environment based on requirements.txt + # && pip --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ + # && rm -rf /tmp/pip-tmp \ + # + # Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user. + && groupadd --gid $USER_GID $USERNAME \ + && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ + # [Optional] Add sudo support for the non-root user + && apt-get install -y sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + # + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +# Switch back to dialog for any ad-hoc use of apt-get +ENV DEBIAN_FRONTEND=dialog + +# USER $USERNAME + + diff --git a/support/ops/relationships/.devcontainer/devcontainer.json b/support/ops/relationships/.devcontainer/devcontainer.json new file mode 100644 index 00000000..36e6606a --- /dev/null +++ b/support/ops/relationships/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.101.0/containers/python-3 +{ + "name": "Python 3", + "context": "..", + "dockerFile": "Dockerfile", + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.pylintPath": "/usr/local/bin/pylint" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python" + ] + + "appPort": [ + 3000, + 8888 + ], + "postCreateCommand": "pip install -r requirements.txt", + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip install -r requirements.txt", + // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. + // "remoteUser": "vscode" +} \ No newline at end of file