Browse Source

bump v0.6.1 - add service templates system

main
def 3 weeks ago
parent
commit
9838c6c615
  1. 12
      PROJECT_STATE.md
  2. 13
      README.md
  3. 2
      VERSION
  4. 213
      backend/app.py
  5. 57
      backend/routes/portal_service_launch.py
  6. 657
      logs/crypto_reconciliation_worker.log
  7. 14
      logs/invoice_reminder_worker.log
  8. 1328
      patch.sh
  9. 115
      patch1.sh
  10. 102
      patch2.sh
  11. 174
      patch3.sh
  12. 143
      patch4.sh
  13. 272
      patch5.sh
  14. 78
      patch6.sh
  15. 157
      patch7.sh
  16. 125
      patch8.sh
  17. 27
      static/css/brand.css
  18. 166
      static/css/style.css
  19. 11
      templatepatch.sh
  20. 98
      templates/service_templates/edit.html
  21. 48
      templates/service_templates/list.html
  22. 98
      templates/service_templates/new.html
  23. 47
      templates/services/edit.html
  24. 5
      templates/services/list.html
  25. 48
      templates/services/new.html

12
PROJECT_STATE.md

@ -1,3 +1,15 @@
## v0.6.1 - Service Templates Phase 1
- Added service_templates table
- Implemented admin CRUD routes in app.py
- Added templates UI pages
- Integrated template selection into services/new and services/edit
- Auto-fill JS implemented for template selection
Status: FUNCTIONAL
Next: link templates to services + invoice integration
---
# Project State Update - v0.6.0 # Project State Update - v0.6.0
Updated: 2026-04-11 01:49:22 UTC Updated: 2026-04-11 01:49:22 UTC

13
README.md

@ -1,3 +1,16 @@
## v0.6.1 - 2026-04-18
### Added
- Service Templates system (standalone pricing catalog)
- Admin UI for managing reusable service pricing
- Template selector on service create/edit pages (auto-fill fields)
### Notes
- Templates are not yet linked to services via template_id (planned)
- Setup amount stored in templates for future invoice integration
- Maintains compatibility with existing services table
---
## v0.6.0 - 2026-04-11 01:49:22 ## v0.6.0 - 2026-04-11 01:49:22
### Highlights ### Highlights

2
VERSION

@ -1 +1 @@
v0.6.0 v0.6.1

213
backend/app.py

@ -12,6 +12,7 @@ from pathlib import Path
from email.message import EmailMessage from email.message import EmailMessage
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from routes.portal_services import portal_services_bp from routes.portal_services import portal_services_bp
from routes.portal_service_launch import portal_service_launch_bp
from io import BytesIO, StringIO from io import BytesIO, StringIO
import csv import csv
@ -42,6 +43,7 @@ app = Flask(
static_folder="../static", static_folder="../static",
) )
app.register_blueprint(portal_services_bp) app.register_blueprint(portal_services_bp)
app.register_blueprint(portal_service_launch_bp)
app.config["OTB_HEALTH_DB_CONNECTOR"] = get_db_connection app.config["OTB_HEALTH_DB_CONNECTOR"] = get_db_connection
TERMS_VERSION = "v1.0" TERMS_VERSION = "v1.0"
@ -2774,8 +2776,15 @@ def new_service():
cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name ASC") cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name ASC")
clients = cursor.fetchall() clients = cursor.fetchall()
cursor.execute("""
SELECT id, template_name, service_type, billing_cycle, currency_code, recurring_amount, setup_amount, description
FROM service_templates
WHERE is_active = 1
ORDER BY template_name ASC
""")
templates = cursor.fetchall()
conn.close() conn.close()
return render_template("services/new.html", clients=clients) return render_template("services/new.html", clients=clients, templates=templates)
@app.route("/services/edit/<int:service_id>", methods=["GET", "POST"]) @app.route("/services/edit/<int:service_id>", methods=["GET", "POST"])
def edit_service(service_id): def edit_service(service_id):
@ -2832,9 +2841,16 @@ def edit_service(service_id):
cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name ASC") cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name ASC")
clients = cursor.fetchall() clients = cursor.fetchall()
cursor.execute("""
SELECT id, template_name, service_type, billing_cycle, currency_code, recurring_amount, setup_amount, description
FROM service_templates
WHERE is_active = 1
ORDER BY template_name ASC
""")
templates = cursor.fetchall()
conn.close() conn.close()
return render_template("services/edit.html", service=service, clients=clients, errors=errors) return render_template("services/edit.html", service=service, clients=clients, templates=templates, errors=errors)
update_cursor = conn.cursor() update_cursor = conn.cursor()
update_cursor.execute(""" update_cursor.execute("""
@ -2875,13 +2891,204 @@ def edit_service(service_id):
cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name ASC") cursor.execute("SELECT id, client_code, company_name FROM clients ORDER BY company_name ASC")
clients = cursor.fetchall() clients = cursor.fetchall()
cursor.execute("""
SELECT id, template_name, service_type, billing_cycle, currency_code, recurring_amount, setup_amount, description
FROM service_templates
WHERE is_active = 1
ORDER BY template_name ASC
""")
templates = cursor.fetchall()
conn.close() conn.close()
if not service: if not service:
return "Service not found", 404 return "Service not found", 404
return render_template("services/edit.html", service=service, clients=clients, errors=[]) return render_template("services/edit.html", service=service, clients=clients, templates=templates, errors=[])
@app.route("/service-templates")
def service_templates():
gate = admin_required()
if gate:
return gate
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
cursor.execute("""
SELECT *
FROM service_templates
ORDER BY id DESC
""")
templates = cursor.fetchall()
conn.close()
return render_template("service_templates/list.html", templates=templates)
@app.route("/service-templates/new", methods=["GET", "POST"])
def new_service_template():
gate = admin_required()
if gate:
return gate
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
errors = []
if request.method == "POST":
template_name = request.form.get("template_name", "").strip()
service_type = request.form.get("service_type", "").strip()
billing_cycle = request.form.get("billing_cycle", "").strip()
currency_code = request.form.get("currency_code", "").strip()
recurring_amount = request.form.get("recurring_amount", "").strip()
setup_amount = request.form.get("setup_amount", "").strip()
description = request.form.get("description", "").strip()
is_active = 1 if request.form.get("is_active") == "1" else 0
if not template_name:
errors.append("Template name is required.")
if not service_type:
errors.append("Service type is required.")
if not billing_cycle:
errors.append("Billing cycle is required.")
if not currency_code:
errors.append("Currency code is required.")
if recurring_amount == "":
errors.append("Recurring amount is required.")
if setup_amount == "":
errors.append("Setup amount is required.")
if not errors:
try:
recurring_amount_value = float(recurring_amount)
setup_amount_value = float(setup_amount)
if recurring_amount_value < 0:
errors.append("Recurring amount cannot be negative.")
if setup_amount_value < 0:
errors.append("Setup amount cannot be negative.")
except ValueError:
errors.append("Amounts must be valid numbers.")
if not errors:
insert_cursor = conn.cursor()
insert_cursor.execute("""
INSERT INTO service_templates
(
template_name,
service_type,
billing_cycle,
currency_code,
recurring_amount,
setup_amount,
description,
is_active
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (
template_name,
service_type,
billing_cycle,
currency_code,
recurring_amount,
setup_amount,
description or None,
is_active
))
conn.commit()
conn.close()
return redirect("/service-templates")
conn.close()
return render_template("service_templates/new.html", errors=errors)
@app.route("/service-templates/edit/<int:template_id>", methods=["GET", "POST"])
def edit_service_template(template_id):
gate = admin_required()
if gate:
return gate
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
if request.method == "POST":
template_name = request.form.get("template_name", "").strip()
service_type = request.form.get("service_type", "").strip()
billing_cycle = request.form.get("billing_cycle", "").strip()
currency_code = request.form.get("currency_code", "").strip()
recurring_amount = request.form.get("recurring_amount", "").strip()
setup_amount = request.form.get("setup_amount", "").strip()
description = request.form.get("description", "").strip()
is_active = 1 if request.form.get("is_active") == "1" else 0
errors = []
if not template_name:
errors.append("Template name is required.")
if not service_type:
errors.append("Service type is required.")
if not billing_cycle:
errors.append("Billing cycle is required.")
if not currency_code:
errors.append("Currency code is required.")
if recurring_amount == "":
errors.append("Recurring amount is required.")
if setup_amount == "":
errors.append("Setup amount is required.")
if not errors:
try:
recurring_amount_value = float(recurring_amount)
setup_amount_value = float(setup_amount)
if recurring_amount_value < 0:
errors.append("Recurring amount cannot be negative.")
if setup_amount_value < 0:
errors.append("Setup amount cannot be negative.")
except ValueError:
errors.append("Amounts must be valid numbers.")
if errors:
cursor.execute("SELECT * FROM service_templates WHERE id = %s", (template_id,))
template = cursor.fetchone()
conn.close()
return render_template("service_templates/edit.html", template=template, errors=errors)
update_cursor = conn.cursor()
update_cursor.execute("""
UPDATE service_templates
SET template_name = %s,
service_type = %s,
billing_cycle = %s,
currency_code = %s,
recurring_amount = %s,
setup_amount = %s,
description = %s,
is_active = %s
WHERE id = %s
""", (
template_name,
service_type,
billing_cycle,
currency_code,
recurring_amount,
setup_amount,
description or None,
is_active,
template_id
))
conn.commit()
conn.close()
return redirect("/service-templates")
cursor.execute("SELECT * FROM service_templates WHERE id = %s", (template_id,))
template = cursor.fetchone()
conn.close()
if not template:
return "Service template not found", 404
return render_template("service_templates/edit.html", template=template, errors=[])

57
backend/routes/portal_service_launch.py

@ -0,0 +1,57 @@
import os
import json
import time
import uuid
import hmac
import base64
import hashlib
from urllib.parse import quote
from flask import Blueprint, session, redirect, url_for, flash
portal_service_launch_bp = Blueprint("portal_service_launch", __name__)
def _b64url_encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("utf-8")
def _sign_payload(payload: dict, secret: str) -> str:
payload_json = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")
payload_b64 = _b64url_encode(payload_json)
sig = hmac.new(secret.encode("utf-8"), payload_b64.encode("utf-8"), hashlib.sha256).digest()
sig_b64 = _b64url_encode(sig)
return f"{payload_b64}.{sig_b64}"
@portal_service_launch_bp.route("/portal/services/follow-me-launch")
def portal_follow_me_launch():
portal_client_id = session.get("portal_client_id")
portal_email = session.get("portal_email")
if not portal_client_id or not portal_email:
flash("Please sign in to launch Follow-me.", "warning")
return redirect(url_for("portal_login"))
secret = os.getenv("FMV2_HANDOFF_SECRET", "").strip()
base_url = os.getenv("FMV2_BASE_URL", "https://follow-me.outsidethebox.top").strip().rstrip("/")
ttl_seconds = int(os.getenv("FMV2_HANDOFF_TTL_SECONDS", "300"))
if not secret:
flash("Follow-me launch is not configured. Missing FMV2_HANDOFF_SECRET.", "danger")
return redirect(url_for("portal_services.portal_services_home"))
now = int(time.time())
payload = {
"iss": "otb-billing",
"aud": "fmv2",
"jti": str(uuid.uuid4()),
"iat": now,
"exp": now + ttl_seconds,
"portal_client_id": int(portal_client_id),
"portal_email": portal_email,
"portal_contact_name": session.get("portal_contact_name", ""),
"portal_company_name": session.get("portal_company_name", ""),
"return_to": "/dashboard",
}
token = _sign_payload(payload, secret)
target = f"{base_url}/auth/portal-handoff?token={quote(token)}"
return redirect(target)

657
logs/crypto_reconciliation_worker.log

@ -1935,3 +1935,660 @@ crypto reconciliation complete mode=interval scanned=0 resolved=0 flagged=0
[2026-04-11T01:03:40.531627+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0 [2026-04-11T01:03:40.531627+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T01:19:30.117863+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0 [2026-04-11T01:19:30.117863+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T01:34:40.057937+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0 [2026-04-11T01:34:40.057937+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T01:49:53.278229+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T02:05:40.141029+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T02:20:40.511784+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T02:36:40.092185+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T02:52:30.100577+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T03:07:40.051218+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T03:22:40.464994+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T03:38:40.067342+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T03:54:40.065044+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T04:00:16.366684+00:00] crypto reconciliation complete mode=daily scanned=13 resolved=0 flagged=0
[2026-04-11T04:09:40.514148+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T04:25:30.086771+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T04:40:40.066913+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T04:56:40.162680+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T05:12:40.067304+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T05:28:30.119535+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T05:43:40.086247+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T05:58:40.506573+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T06:14:39.952502+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T06:30:40.130552+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T06:46:29.988648+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T07:01:40.156235+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T07:16:40.363390+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T07:32:40.192332+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T07:47:40.438257+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T08:02:45.124915+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T08:18:40.080213+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T08:34:30.107423+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T08:49:39.947765+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T09:04:40.560552+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T09:20:40.097301+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T09:36:40.057695+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T09:51:40.513234+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T10:07:30.131923+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T10:22:39.984910+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T10:37:40.647453+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T10:53:40.060926+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T11:08:40.507498+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T11:24:40.043846+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T11:39:40.619284+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T11:55:30.000581+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T12:10:40.126529+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T12:26:40.120968+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T12:42:40.086522+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T12:58:30.060397+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T13:13:40.115147+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T13:28:40.452990+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T13:44:40.090979+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T14:00:40.096525+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T14:16:04.314597+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T14:31:30.439188+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T14:46:40.069365+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T15:02:39.983170+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T15:17:40.501338+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T15:33:40.111376+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T15:48:40.507935+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T16:04:30.086809+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T16:19:40.016152+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T16:34:40.424593+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T16:50:40.280789+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T17:06:40.776224+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T17:21:40.554451+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T17:37:30.029263+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T17:52:40.072909+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T18:08:40.024084+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T18:24:40.218531+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T18:40:30.163810+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T18:55:40.137094+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T19:10:40.523993+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T19:26:40.179418+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T19:41:40.485329+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T19:57:40.231886+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T20:12:40.567083+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T20:28:30.470593+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T20:43:40.063669+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T20:58:40.589750+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T21:13:47.418613+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T21:28:49.192899+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T21:44:17.661196+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T21:59:20.653478+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T22:14:40.005300+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T22:29:40.879564+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T22:45:40.050664+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T23:00:40.665970+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T23:16:04.535194+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T23:31:30.137037+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-11T23:46:40.094731+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T00:02:30.578525+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T00:17:40.037746+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T00:32:40.545409+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T00:48:59.925769+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T01:04:30.253092+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T01:19:40.209754+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T01:34:40.534930+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T01:50:40.076983+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T02:06:40.104425+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T02:22:29.942937+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T02:37:40.144848+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T02:52:40.452520+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T03:08:40.123605+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T03:24:40.028986+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T03:40:30.173497+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T03:55:41.914541+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T04:00:21.795042+00:00] crypto reconciliation complete mode=daily scanned=13 resolved=0 flagged=0
[2026-04-12T04:10:40.562699+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T04:26:40.429861+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T04:42:40.106792+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T04:58:30.017161+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T05:13:40.100404+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T05:28:40.454529+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T05:44:40.149869+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T06:00:40.110709+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T06:15:43.488132+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T06:31:30.075767+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T06:46:40.079531+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T07:02:39.897594+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T07:18:40.164837+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T07:33:40.464008+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T07:49:30.051976+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T08:04:39.960982+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T08:19:40.551864+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T08:34:58.262670+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T08:50:40.011084+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T09:06:40.010273+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T09:21:40.690376+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T09:37:30.029649+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T09:52:40.195534+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T10:08:40.033584+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T10:24:40.117275+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T10:39:40.422761+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T10:55:30.058239+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T11:10:30.378718+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T11:25:40.041984+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T11:40:40.471124+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T11:56:40.127899+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T12:11:40.370214+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T12:27:40.042869+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T12:42:40.411754+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T12:58:30.120712+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T13:13:40.021405+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T13:28:40.576070+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T13:44:06.436865+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T13:59:40.043153+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T14:14:40.429140+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T14:30:40.084291+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T14:46:29.961774+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T15:01:47.078756+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T15:16:40.505394+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T15:32:40.328812+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T15:48:40.368641+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T16:04:30.068324+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T16:19:40.116236+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T16:34:40.603246+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T16:50:40.134258+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T17:05:40.633867+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T17:21:40.283607+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T17:36:40.639503+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T17:52:30.132461+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T18:07:40.172052+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T18:22:40.696651+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T18:38:40.314289+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T18:54:40.205575+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T19:09:40.715936+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T19:25:30.261212+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T19:40:40.326370+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T19:55:40.518647+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T20:11:40.181355+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T20:26:40.546811+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T20:42:40.155997+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T20:57:46.487871+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T21:13:30.233855+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T21:28:40.263986+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T21:44:40.111985+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T22:00:40.056307+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T22:16:04.220906+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T22:31:30.146504+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T22:46:40.116999+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T23:02:40.067266+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T23:17:40.574968+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T23:33:40.391102+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-12T23:48:40.653404+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T00:04:30.114333+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T00:19:40.227423+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T00:34:40.863398+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T00:50:40.253081+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T01:05:40.619985+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T01:21:34.960211+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T01:36:40.217628+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T01:52:30.153935+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T02:07:40.107998+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T02:22:40.627306+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T02:38:40.084510+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T02:54:40.132440+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T03:10:30.493987+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T03:25:40.164450+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T03:40:40.617606+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T03:56:40.232397+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T04:00:16.376539+00:00] crypto reconciliation complete mode=daily scanned=13 resolved=0 flagged=0
[2026-04-13T04:12:40.081352+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T04:28:30.205327+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T04:43:40.087733+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T04:58:40.581018+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T05:13:58.669260+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T05:29:40.086671+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T05:44:40.538480+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T06:00:40.165632+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T06:16:04.411910+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T06:31:30.180258+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T06:46:40.153858+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T07:02:40.145503+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T07:18:40.117573+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T07:34:30.219782+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T07:49:40.116982+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T08:04:40.536908+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T08:20:40.113003+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T08:36:40.212387+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T08:52:30.194906+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T09:07:40.167468+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T09:22:40.509196+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T09:38:40.147965+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T09:54:40.151269+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T10:09:40.637206+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T10:25:30.178376+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T10:40:40.283555+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T10:55:40.572205+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T11:11:40.151968+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T11:26:40.625718+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T11:42:40.159666+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T11:58:30.218765+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T12:13:40.249932+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T12:28:40.640427+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T12:45:01.025976+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T13:00:40.140187+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T13:15:40.665763+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T13:31:30.142440+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T13:46:40.204780+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T14:02:40.187097+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T14:18:40.246361+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T14:33:40.547954+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T14:49:30.231058+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T15:04:40.184076+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T15:19:40.641289+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T15:35:40.154715+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T15:50:40.614451+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T16:06:40.064727+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T16:22:30.093148+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T16:37:40.177133+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T16:52:40.731632+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T17:08:40.121628+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T17:24:40.121342+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T17:40:30.473598+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T17:55:40.162708+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T18:10:40.571705+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T18:26:40.159328+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T18:42:40.077560+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T18:57:40.627430+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T19:13:30.045751+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T19:28:40.162135+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T19:44:40.179203+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T20:00:40.594751+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T20:16:04.820002+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T20:31:30.211455+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T20:46:40.072757+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T21:01:40.628012+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T21:17:40.104155+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T21:32:40.608171+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T21:48:40.053725+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T22:04:30.044580+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T22:19:40.065314+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T22:34:40.568803+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T22:50:42.589389+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T23:06:39.990952+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T23:22:30.058945+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T23:37:40.171840+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-13T23:52:40.473641+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T00:08:40.146525+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T00:24:40.129412+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T00:40:30.104410+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T00:55:41.198391+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T01:10:40.602018+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T01:26:40.339871+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T01:41:40.662939+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T01:57:40.051325+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T02:12:40.513395+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T02:28:30.130054+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T02:43:40.125822+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T02:58:40.460979+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T03:14:40.069739+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T03:30:39.995071+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T03:45:40.576758+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T04:00:16.322016+00:00] crypto reconciliation complete mode=daily scanned=13 resolved=0 flagged=0
[2026-04-14T04:01:08.189627+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T04:16:40.092801+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T04:32:40.038027+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T04:47:40.587003+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T05:03:40.137769+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T05:18:40.501566+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T05:34:30.038507+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T05:49:40.101287+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T06:04:40.489839+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T06:20:40.124456+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T06:36:40.073983+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T06:52:30.137896+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T07:07:40.134850+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T07:22:40.461562+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T07:38:40.153526+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T07:54:40.136863+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T08:09:40.607440+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T08:25:30.142733+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T08:40:40.085705+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T08:56:40.154320+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T09:11:40.890995+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T09:27:40.131570+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T09:42:40.385000+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T09:58:30.208141+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T10:13:40.024667+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T10:28:40.671432+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T10:44:40.080357+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T11:00:40.072741+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T11:16:04.134124+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T11:31:30.074047+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T11:46:40.131034+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T12:01:40.525845+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T12:17:39.995881+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T12:32:40.564288+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T12:48:40.052419+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T13:03:40.512932+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T13:19:29.988599+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T13:34:40.128212+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T13:49:40.588474+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T14:05:40.216612+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T14:20:40.448654+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T14:36:40.146717+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T14:52:30.003241+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T15:07:40.103542+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T15:22:40.454089+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T15:38:40.085868+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T15:53:40.545534+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T16:09:40.202305+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T16:24:40.508234+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T16:40:30.365942+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T16:55:40.110415+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T17:10:40.558907+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T17:26:40.111463+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T17:42:40.110612+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T17:58:29.942655+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T18:13:40.110569+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T18:28:40.454377+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T18:44:40.098088+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T19:00:40.172773+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T19:16:04.340096+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T19:31:30.176615+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T19:46:40.087879+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T20:02:39.959994+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T20:18:40.115070+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T20:34:30.016393+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T20:49:40.479728+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T21:04:40.466614+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T21:20:40.100538+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T21:36:40.141281+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T21:52:30.172831+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T22:07:40.105911+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T22:22:40.581717+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T22:38:16.499157+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T22:53:40.050041+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T23:08:40.542188+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T23:24:40.088437+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T23:39:40.537225+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-14T23:55:30.246617+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T00:10:40.041905+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T00:26:40.082559+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T00:41:40.525983+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T00:57:40.182121+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T01:12:40.451341+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T01:28:30.750018+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T01:43:40.067359+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T01:58:40.533974+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T02:14:40.036841+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T02:29:40.533772+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T02:45:40.088207+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T03:00:40.453470+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T03:16:04.527976+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T03:31:30.073369+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T03:46:40.034207+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T04:00:16.329498+00:00] crypto reconciliation complete mode=daily scanned=13 resolved=0 flagged=0
[2026-04-15T04:02:30.532256+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T04:17:40.146127+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T04:32:40.582885+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T04:48:40.103885+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T05:04:30.152491+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T05:19:40.118147+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T05:34:40.515393+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T05:50:40.124500+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T06:06:40.115104+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T06:21:40.558161+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T06:37:30.084955+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T06:52:40.181313+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T07:08:40.251011+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T07:24:40.158739+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T07:40:30.237790+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T07:55:40.163564+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T08:10:40.667102+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T08:26:40.086813+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T08:41:40.564912+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T08:57:40.159326+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T09:12:40.492842+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T09:28:30.126958+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T09:43:40.126157+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T09:58:40.513637+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T10:14:40.249478+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T10:29:40.593862+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T10:45:40.049018+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T11:00:40.603942+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T11:16:04.790250+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T11:31:30.565720+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T11:46:40.167248+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T12:02:40.148348+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T12:17:40.602346+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T12:33:40.037647+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T12:48:40.540027+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T13:04:30.139847+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T13:19:40.229418+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T13:34:40.620420+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T13:50:40.127021+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T14:05:41.570919+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T14:21:40.096975+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T14:36:40.628309+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T14:52:30.193542+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T15:07:40.103992+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T15:22:40.519320+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T15:38:40.160700+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T15:54:40.168891+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T16:09:40.408635+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T16:25:30.163678+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T16:40:40.090405+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T16:55:40.489425+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T17:11:40.521318+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T17:26:40.595504+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T17:42:39.986413+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T17:58:30.186083+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T18:13:39.951306+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T18:28:40.533715+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T18:44:40.005624+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T19:00:40.196511+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T19:16:04.971426+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T19:31:30.325850+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T19:46:40.198759+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T20:02:40.243123+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T20:18:40.331580+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T20:34:30.132675+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T20:49:40.159038+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T21:04:40.603999+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T21:20:41.142916+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T21:36:40.209587+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T21:51:40.793016+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T22:07:30.289250+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T22:22:40.047317+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T22:38:40.108840+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T22:54:40.041707+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T23:10:30.105723+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T23:25:40.104900+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T23:40:40.594261+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-15T23:56:40.916082+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T00:12:40.077182+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T00:28:30.077395+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T00:43:40.228776+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T00:58:40.388740+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T01:14:40.159402+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T01:30:40.083836+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T01:46:30.159641+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T02:01:40.169410+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T02:16:40.575877+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T02:32:41.217708+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T02:47:40.658041+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T03:03:40.102230+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T03:18:41.143861+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T03:34:30.225126+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T03:49:40.188070+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T04:00:17.188815+00:00] crypto reconciliation complete mode=daily scanned=13 resolved=0 flagged=0
[2026-04-16T04:04:41.838617+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T04:20:40.558289+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T04:36:41.006094+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T04:52:30.483668+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T05:07:40.312775+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T05:22:40.912025+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T05:38:40.289117+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T05:54:40.358820+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T06:13:47.466428+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T06:30:48.158996+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T06:40:40.667394+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T06:56:41.373109+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T07:12:40.337260+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T07:27:40.757140+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T07:43:30.318422+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T07:58:40.343998+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T08:14:40.323222+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T08:29:40.795881+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T08:45:40.321295+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T09:00:40.477596+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T09:16:04.327347+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T09:31:30.039179+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T09:46:40.085981+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T10:02:40.078941+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T10:17:40.629778+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T10:33:40.298899+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T10:48:40.482877+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T11:04:30.077799+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T11:19:40.181971+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T11:34:40.433806+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T11:50:40.183245+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T12:06:40.162793+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T12:21:40.683272+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T12:37:31.142792+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T12:52:41.439136+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T13:08:40.019640+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T13:24:40.211334+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T13:39:40.552158+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T13:55:30.046046+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T14:10:40.078872+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T14:26:40.106177+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T14:42:40.106651+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T14:58:30.193991+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T15:13:40.081950+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T15:28:40.537708+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T15:44:40.027718+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T15:59:40.613297+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T16:15:40.041673+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T16:30:40.645807+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T16:46:16.444296+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T17:01:30.154170+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T17:16:40.026347+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T17:32:40.048222+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T17:48:40.198617+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T18:03:40.562926+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T18:19:30.163839+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T18:34:40.175649+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T18:50:40.048942+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T19:06:40.101085+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T19:21:40.605054+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T19:37:30.185144+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T19:52:42.234639+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T20:08:40.185105+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T20:23:41.537646+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T20:39:40.154305+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T20:54:40.544021+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T21:10:30.179139+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T21:25:40.183454+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T21:40:40.533922+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T21:56:40.248117+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T22:12:40.129323+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T22:28:30.187039+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T22:43:40.118537+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T22:58:40.568764+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T23:14:02.515764+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T23:29:40.124931+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-16T23:44:40.554848+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T00:00:16.661776+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T00:15:40.075457+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T00:30:40.624456+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T00:46:30.203746+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T01:01:40.187141+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T01:16:40.545189+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T01:32:40.177048+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T01:47:40.627561+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T02:03:40.194047+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T02:18:40.559143+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T02:34:12.128162+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T02:49:30.774712+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T03:04:40.282699+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T03:20:40.304075+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T03:36:40.106180+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T03:52:19.006682+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T04:00:16.380527+00:00] crypto reconciliation complete mode=daily scanned=13 resolved=0 flagged=0
[2026-04-17T04:07:30.122713+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T04:22:40.065421+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T04:38:40.073477+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T04:54:40.111120+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T05:10:30.021762+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T05:25:40.069102+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T05:40:40.481276+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T05:56:40.223278+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T06:11:40.468922+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T06:27:40.078534+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T06:42:40.535859+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T06:58:30.122045+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T07:13:40.199346+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T07:28:40.540270+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T07:44:40.097781+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T08:00:40.150693+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T08:16:04.863391+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T08:31:30.173016+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T08:46:40.203737+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T09:02:30.983235+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T09:17:40.138825+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T09:32:40.655583+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T09:48:41.117605+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T10:03:40.629448+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T10:19:30.104491+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T10:34:41.084177+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T10:50:40.120053+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T11:06:41.644493+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T11:22:30.171427+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T11:37:40.343789+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T11:52:40.549893+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T12:08:40.129540+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T12:24:40.167578+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T12:40:30.136398+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T12:55:40.099260+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T13:10:40.640777+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T13:26:40.150723+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T13:42:40.790928+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T13:58:30.403132+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T14:13:41.084522+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T14:28:40.513331+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T14:44:40.128032+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T15:00:40.198992+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T15:16:04.885444+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T15:31:30.084090+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T15:46:40.200551+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T16:01:40.606746+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T16:17:40.099968+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T16:32:41.253624+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T16:48:40.185046+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T17:04:30.142643+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T17:19:40.173649+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T17:34:40.474625+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T17:50:40.159707+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T18:06:40.022551+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T18:22:30.165062+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T18:37:40.048612+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T18:52:40.503816+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T19:08:40.143531+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T19:24:40.089949+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T19:40:30.244522+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T19:55:40.170838+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T20:10:40.599761+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T20:26:40.151328+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T20:42:40.038579+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T20:58:30.194446+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T21:13:40.050821+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T21:28:41.083581+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T21:44:40.630331+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T21:59:40.903358+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T22:15:40.219106+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T22:30:40.924894+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T22:46:30.397096+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T23:01:40.507226+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T23:16:40.901377+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T23:32:40.491545+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-17T23:48:40.339345+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-18T00:03:43.119979+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-18T00:18:50.218164+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-18T00:34:30.348400+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-18T00:49:40.481461+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-18T01:04:41.085178+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-18T01:19:51.265124+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0
[2026-04-18T01:35:40.601272+00:00] crypto reconciliation complete mode=interval scanned=13 resolved=0 flagged=0

14
logs/invoice_reminder_worker.log

@ -89,3 +89,17 @@ TypeError: 'NoneType' object is not subscriptable
[2026-04-09T09:00:14.906306] checked=0 reminders_sent=0 overdue_sent=0 skipped=0 [2026-04-09T09:00:14.906306] checked=0 reminders_sent=0 overdue_sent=0 skipped=0
[2026-04-10T09:00:14.318241] invoice_reminder_worker starting [2026-04-10T09:00:14.318241] invoice_reminder_worker starting
[2026-04-10T09:00:14.322748] checked=0 reminders_sent=0 overdue_sent=0 skipped=0 [2026-04-10T09:00:14.322748] checked=0 reminders_sent=0 overdue_sent=0 skipped=0
[2026-04-11T09:00:14.565546] invoice_reminder_worker starting
[2026-04-11T09:00:14.570056] checked=0 reminders_sent=0 overdue_sent=0 skipped=0
[2026-04-12T09:00:14.474926] invoice_reminder_worker starting
[2026-04-12T09:00:14.479514] checked=0 reminders_sent=0 overdue_sent=0 skipped=0
[2026-04-13T09:00:14.394240] invoice_reminder_worker starting
[2026-04-13T09:00:14.398664] checked=0 reminders_sent=0 overdue_sent=0 skipped=0
[2026-04-14T09:00:14.661985] invoice_reminder_worker starting
[2026-04-14T09:00:14.666496] checked=0 reminders_sent=0 overdue_sent=0 skipped=0
[2026-04-15T09:00:14.627345] invoice_reminder_worker starting
[2026-04-15T09:00:14.631650] checked=0 reminders_sent=0 overdue_sent=0 skipped=0
[2026-04-16T09:00:14.632830] invoice_reminder_worker starting
[2026-04-16T09:00:14.638602] checked=0 reminders_sent=0 overdue_sent=0 skipped=0
[2026-04-17T09:00:14.603365] invoice_reminder_worker starting
[2026-04-17T09:00:14.607816] checked=0 reminders_sent=0 overdue_sent=0 skipped=0

1328
patch.sh

File diff suppressed because it is too large Load Diff

115
patch1.sh

@ -1,57 +1,70 @@
cd /home/def/otb_billing || exit 1 cd /home/def/otb_billing || exit 1
set -e
STAMP="$(date +%Y%m%d-%H%M%S)" STAMP=$(date +%Y%m%d-%H%M%S)
NEWVER="v0.6.1"
cp templates/includes/otb_statusbar.html "templates/includes/otb_statusbar.html.bak.${STAMP}" 2>/dev/null || true echo "===== backup full project ====="
cp templates/portal_login.html "templates/portal_login.html.bak.footer.${STAMP}" mkdir -p /home/def/backuphere
cp templates/portal_dashboard.html "templates/portal_dashboard.html.bak.footer.${STAMP}" 2>/dev/null || true cd /home/def
cp templates/portal_set_password.html "templates/portal_set_password.html.bak.footer.${STAMP}" 2>/dev/null || true tar -czf /home/def/backuphere/otb_billing-${NEWVER}-${STAMP}.tar.gz otb_billing
cp templates/portal_terms.html "templates/portal_terms.html.bak.footer.${STAMP}" 2>/dev/null || true
cp templates/portal_invoice_detail.html "templates/portal_invoice_detail.html.bak.footer.${STAMP}" 2>/dev/null || true
cp templates/portal_forgot_password.html "templates/portal_forgot_password.html.bak.footer.${STAMP}" 2>/dev/null || true
cat > templates/includes/otb_footer.html <<'EOF' echo "===== update VERSION ====="
{% include "includes/otb_statusbar.html" %} cd /home/def/otb_billing || exit 1
<script src="/static/brand.js" defer></script> echo "${NEWVER}" > VERSION
echo "===== update README.md ====="
cp README.md /home/def/backuphere/README.md.${STAMP}.bak
cat > /tmp/readme_entry.txt <<EOF
## ${NEWVER} - $(date +%Y-%m-%d)
### Added
- Service Templates system (standalone pricing catalog)
- Admin UI for managing reusable service pricing
- Template selector on service create/edit pages (auto-fill fields)
### Notes
- Templates are not yet linked to services via template_id (planned)
- Setup amount stored in templates for future invoice integration
- Maintains compatibility with existing services table
---
EOF EOF
python3 <<'PY' cat /tmp/readme_entry.txt README.md > README.md.new
from pathlib import Path mv README.md.new README.md
import re
echo "===== update PROJECT_STATE.md ====="
files = [ cp PROJECT_STATE.md /home/def/backuphere/PROJECT_STATE.md.${STAMP}.bak
Path("templates/portal_login.html"),
Path("templates/portal_dashboard.html"), cat > /tmp/state_entry.txt <<EOF
Path("templates/portal_set_password.html"), ## ${NEWVER} - Service Templates Phase 1
Path("templates/portal_terms.html"),
Path("templates/portal_invoice_detail.html"), - Added service_templates table
Path("templates/portal_forgot_password.html"), - Implemented admin CRUD routes in app.py
] - Added templates UI pages
- Integrated template selection into services/new and services/edit
for p in files: - Auto-fill JS implemented for template selection
if not p.exists():
continue Status: FUNCTIONAL
text = p.read_text() Next: link templates to services + invoice integration
text = re.sub( ---
r'\{\%\s*include\s+"includes/otb_statusbar\.html"\s*\%\}\s*<script src="/static/brand\.js" defer></script>', EOF
'{% include "includes/otb_footer.html" %}',
text, cat /tmp/state_entry.txt PROJECT_STATE.md > PROJECT_STATE.md.new
count=1, mv PROJECT_STATE.md.new PROJECT_STATE.md
flags=re.S
) echo "===== git status ====="
git status
text = re.sub(
r'\{\%\s*include\s+"includes/otb_statusbar\.html"\s*\%\}', echo "===== commit ====="
'{% include "includes/otb_footer.html" %}', git add .
text, git commit -m "bump ${NEWVER} - add service templates system"
count=1,
flags=re.S echo "===== push ====="
) git push
p.write_text(text) echo "===== done ====="
print(f"updated: {p}") echo "Backup saved at:"
PY ls -lh /home/def/backuphere/otb_billing-${NEWVER}-${STAMP}.tar.gz
sudo systemctl restart otb_billing

102
patch2.sh

@ -1,102 +0,0 @@
cd /home/def/otb_billing || exit 1
set -e
STAMP="$(date +%Y%m%d-%H%M%S)"
echo "===== backups ====="
cp templates/includes/site_nav.html "templates/includes/site_nav.html.bak.${STAMP}"
cp templates/includes/otb_statusbar.html "templates/includes/otb_statusbar.html.bak.${STAMP}" 2>/dev/null || true
cp templates/portal_login.html "templates/portal_login.html.bak.${STAMP}"
cp templates/portal_dashboard.html "templates/portal_dashboard.html.bak.${STAMP}" 2>/dev/null || true
cp templates/portal_set_password.html "templates/portal_set_password.html.bak.${STAMP}" 2>/dev/null || true
cp templates/portal_terms.html "templates/portal_terms.html.bak.${STAMP}" 2>/dev/null || true
cp templates/portal_invoice_detail.html "templates/portal_invoice_detail.html.bak.${STAMP}" 2>/dev/null || true
cp templates/portal_forgot_password.html "templates/portal_forgot_password.html.bak.${STAMP}" 2>/dev/null || true
cp static/css/style.css "static/css/style.css.bak.${STAMP}" 2>/dev/null || true
cp static/brand.js "static/brand.js.bak.${STAMP}" 2>/dev/null || true
echo "===== sync shared nav/footer assets from mintme-backed shared brand ====="
cp /opt/otb/otb-shared-brand/fragments/header.html templates/includes/site_nav.html
cat > templates/includes/otb_footer.html <<'EOF'
<div class="otb-statusbar">
<div class="otb-statusbar-inner">
<strong>All billing is calculated in 🇨🇦 CAD</strong>
<span class="otb-dot"></span>
<span>Crypto conversions use the <a href="https://monitor.outsidethebox.top" target="_blank" rel="noopener noreferrer">OTB Oracle</a></span>
<span class="otb-dot"></span>
<span>
Methods:
<strong>Credit Card</strong> <span style="opacity:0.7;">(via Square)</span>,
<strong>e-Transfer</strong>,
and <strong>enabled crypto assets</strong>
</span>
</div>
</div>
<script src="/static/brand.js" defer></script>
EOF
echo "===== sync shared css/js ====="
cp /var/www/outsidethebox.top/assets/brand.js static/brand.js
python3 <<'PY'
from pathlib import Path
src = Path("/var/www/outsidethebox.top/assets/style.css")
dst = Path("/home/def/otb_billing/static/css/style.css")
text = src.read_text()
# keep the portal's own css file as the shared base for now
dst.write_text(text)
print("synced style.css from shared public shell")
PY
echo "===== replace portal footer/statusbar blocks with shared footer include ====="
python3 <<'PY'
from pathlib import Path
import re
files = [
Path("templates/portal_login.html"),
Path("templates/portal_dashboard.html"),
Path("templates/portal_set_password.html"),
Path("templates/portal_terms.html"),
Path("templates/portal_invoice_detail.html"),
Path("templates/portal_forgot_password.html"),
]
for p in files:
if not p.exists():
continue
text = p.read_text()
text = re.sub(
r'<div class="otb-statusbar">.*?</div>\s*</div>\s*<script src="/static/brand\.js" defer></script>',
'{% include "includes/otb_footer.html" %}',
text,
count=1,
flags=re.S
)
text = re.sub(
r'\{\%\s*include\s+"includes/otb_statusbar\.html"\s*\%\}\s*<script src="/static/brand\.js" defer></script>',
'{% include "includes/otb_footer.html" %}',
text,
count=1,
flags=re.S
)
text = re.sub(
r'\{\%\s*include\s+"includes/otb_statusbar\.html"\s*\%\}',
'{% include "includes/otb_footer.html" %}',
text,
count=1,
flags=re.S
)
p.write_text(text)
print(f"updated: {p}")
PY
sudo systemctl restart otb_billing
echo "===== done ====="

174
patch3.sh

@ -1,174 +0,0 @@
cd /opt/otb_tracker || exit 1
cat > /tmp/otb_tracker_shared_brand_patch.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
APP_ROOT="/opt/otb_tracker"
SHARED_ROOT="/opt/otb/otb-shared-brand"
STAMP="$(date +%Y%m%d-%H%M%S)"
cd "$APP_ROOT" || exit 1
echo "===== detect template/static roots ====="
TPL_ROOT=""
STATIC_ROOT=""
if [ -d "$APP_ROOT/templates" ]; then
TPL_ROOT="$APP_ROOT/templates"
elif [ -d "$APP_ROOT/backend/templates" ]; then
TPL_ROOT="$APP_ROOT/backend/templates"
else
echo "ERROR: no templates dir found"
exit 1
fi
if [ -d "$APP_ROOT/static" ]; then
STATIC_ROOT="$APP_ROOT/static"
elif [ -d "$APP_ROOT/backend/static" ]; then
STATIC_ROOT="$APP_ROOT/backend/static"
else
echo "ERROR: no static dir found"
exit 1
fi
echo "Templates: $TPL_ROOT"
echo "Static: $STATIC_ROOT"
echo "===== ensure shared brand exists and is current ====="
cd "$SHARED_ROOT" || exit 1
git pull origin main
cd "$APP_ROOT" || exit 1
echo "===== backup ====="
mkdir -p "$APP_ROOT/backups/shared-brand-$STAMP"
cp -a "$TPL_ROOT" "$APP_ROOT/backups/shared-brand-$STAMP/templates"
cp -a "$STATIC_ROOT" "$APP_ROOT/backups/shared-brand-$STAMP/static"
echo "===== create includes dir ====="
mkdir -p "$TPL_ROOT/includes"
mkdir -p "$STATIC_ROOT/css"
echo "===== sync shared assets ====="
cp "$SHARED_ROOT/brand.css" "$STATIC_ROOT/css/brand.css"
cp "$SHARED_ROOT/brand.js" "$STATIC_ROOT/brand.js"
echo "===== sync shared header include ====="
cp "$SHARED_ROOT/fragments/header.html" "$TPL_ROOT/includes/site_nav.html"
echo "===== write canonical shared footer includes ====="
cat > "$TPL_ROOT/includes/otb_statusbar.html" <<'EOT'
<div class="otb-statusbar">
<div class="otb-statusbar-inner">
<strong>All billing is calculated in 🇨🇦 CAD</strong>
<span class="otb-dot"></span>
<span>Crypto conversions use the <a href="https://monitor.outsidethebox.top" target="_blank" rel="noopener noreferrer">OTB Oracle</a></span>
<span class="otb-dot"></span>
<span>
Methods:
<strong>Credit Card</strong> <span style="opacity:0.7;">(via Square)</span>,
<strong>e-Transfer</strong>,
and <strong>enabled crypto assets</strong>
</span>
</div>
</div>
EOT
cat > "$TPL_ROOT/includes/otb_footer.html" <<'EOT'
{% include "includes/otb_statusbar.html" %}
<script src="/static/brand.js" defer></script>
EOT
echo "===== patch html templates ====="
python3 <<PY
from pathlib import Path
import re
tpl_root = Path("$TPL_ROOT")
html_files = list(tpl_root.rglob("*.html"))
for p in html_files:
if "includes" in p.parts:
continue
text = p.read_text(encoding="utf-8")
# add shared brand.css after local style.css if missing
text = re.sub(
r'(<link rel="stylesheet" href="/static/css/style\.css"\s*/?>)',
r'\\1\n <link rel="stylesheet" href="/static/css/brand.css?v=0.3.6" />',
text,
count=1
)
text = re.sub(
r'(<link rel="stylesheet" href="/static/css/style\.css"\s*>)',
r'\\1\n <link rel="stylesheet" href="/static/css/brand.css?v=0.3.6" />',
text,
count=1
)
# normalize brand css/js cache versions
text = text.replace('/static/css/brand.css?v=0.3.5', '/static/css/brand.css?v=0.3.6')
text = text.replace('/static/css/brand.css?v=0.3.6?v=0.3.6', '/static/css/brand.css?v=0.3.6')
text = text.replace('/static/brand.js?v=0.3.5', '/static/brand.js?v=0.3.6')
text = text.replace('/static/brand.js?v=0.3.6?v=0.3.6', '/static/brand.js?v=0.3.6')
# replace inline shared header block if present
text = re.sub(
r'<header class="site-header">.*?</header>',
'{% include "includes/site_nav.html" %}',
text,
count=1,
flags=re.S
)
# replace older nav block if present
text = re.sub(
r'<div class="container">\s*<div class="nav">.*?</div>\s*',
'{% include "includes/site_nav.html" %}\n',
text,
count=1,
flags=re.S
)
# if no shared nav include and no obvious header/nav remains, insert after <body>
if '{% include "includes/site_nav.html" %}' not in text:
if '<body>' in text:
text = text.replace('<body>', '<body>\n {% include "includes/site_nav.html" %}', 1)
# replace inline statusbar/footer block with shared footer include
text = re.sub(
r'<div class="otb-statusbar">.*?</div>\s*</div>\s*(<script src="/static/brand\.js".*?</script>)?',
'{% include "includes/otb_footer.html" %}',
text,
count=1,
flags=re.S
)
# if brand.js still directly included, remove it because footer include owns it
text = re.sub(
r'\s*<script src="/static/brand\.js(?:\?v=[^"]*)?" defer></script>\s*',
'\n',
text,
flags=re.S
)
# if footer include absent, append before </body>
if '{% include "includes/otb_footer.html" %}' not in text:
text = text.replace('</body>', ' {% include "includes/otb_footer.html" %}\n</body>', 1)
p.write_text(text, encoding="utf-8")
print(f"patched: {p}")
PY
echo "===== restart service ====="
sudo systemctl restart otb-tracker.service
echo "===== verify ====="
grep -Rni 'includes/site_nav.html\|includes/otb_footer.html\|/static/css/brand.css\|video.outsidethebox.top' "$TPL_ROOT" | sed -n '1,200p'
ls -l "$STATIC_ROOT/css/brand.css" "$STATIC_ROOT/brand.js"
echo "===== done ====="
EOF
chmod +x /tmp/otb_tracker_shared_brand_patch.sh
/tmp/otb_tracker_shared_brand_patch.sh

143
patch4.sh

@ -1,143 +0,0 @@
cd /home/def/otb_billing || exit 1
cp templates/portal/services_here.html templates/portal/services_here.html.bak.$(date +%Y%m%d-%H%M%S)
cat > templates/portal/services_here.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Services - OutsideTheBox</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/brand.css">
<link rel="icon" type="image/png" href="/static/favicon.png">
</head>
<body>
{% include "includes/site_nav.html" %}
<div class="portal-shell">
<div class="portal-wrap">
<div class="portal-page-header">
<div>
<h1 class="portal-page-title">Services</h1>
<p class="portal-client-name">{{ client_name }}</p>
<p class="portal-page-subtitle">Launch available OTB services from one place.</p>
</div>
<div class="portal-toolbar" style="display:flex;flex-direction:column;align-items:flex-end;gap:10px;">
<div style="display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;">
<a class="portal-btn primary" href="/portal/dashboard">Back to Dashboard</a>
<a class="portal-btn" href="/portal/logout">Logout</a>
</div>
<div style="text-align:right;font-size:14px;opacity:0.95;">
<div>Logged in as: <strong>{{ client_name }}</strong></div>
</div>
</div>
</div>
<section class="services-grid">
{% for service in services %}
<article class="service-card status-{{ service.status }}">
<div class="service-card-header">
<div>
<h2>{{ service.name }}</h2>
<p>{{ service.summary }}</p>
</div>
<div>
{% if service.status == 'beta' %}
<span class="service-badge service-badge-beta">Beta</span>
{% elif service.status == 'coming_soon' %}
<span class="service-badge service-badge-soon">Coming Soon</span>
{% else %}
<span class="service-badge">Available</span>
{% endif %}
</div>
</div>
<div class="service-card-actions">
{% if service.enabled %}
<a class="portal-btn primary" href="{{ service.href }}">{{ service.button_text }}</a>
{% else %}
<button class="portal-btn btn-disabled" disabled>{{ service.button_text }}</button>
{% endif %}
</div>
</article>
{% endfor %}
</section>
</div>
</div>
<style>
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
.service-card {
border-radius: 18px;
padding: 22px;
border: 1px solid rgba(255,255,255,0.10);
background: rgba(8, 18, 37, 0.72);
box-shadow: 0 8px 24px rgba(0,0,0,0.18);
}
.service-card-header {
display: flex;
justify-content: space-between;
gap: 16px;
align-items: flex-start;
}
.service-card h2 {
margin: 0 0 10px 0;
font-size: 1.3rem;
}
.service-card p {
margin: 0;
line-height: 1.5;
opacity: 0.92;
}
.service-card-actions {
margin-top: 22px;
}
.service-badge {
display: inline-block;
padding: 6px 10px;
border-radius: 999px;
font-size: 0.82rem;
font-weight: 700;
background: rgba(255,255,255,0.10);
}
.service-badge-beta {
background: rgba(73, 192, 255, 0.18);
}
.service-badge-soon {
background: rgba(255, 196, 73, 0.18);
}
.btn-disabled {
opacity: 0.65;
cursor: not-allowed;
}
</style>
{% include "includes/otb_footer.html" %}
</body>
</html>
EOF
sudo systemctl restart otb_billing.service

272
patch5.sh

@ -1,272 +0,0 @@
cd /home/def/otb_billing || exit 1
set -e
STAMP="$(date +%Y%m%d-%H%M%S)"
cp templates/portal_dashboard.html "templates/portal_dashboard.html.bak.${STAMP}"
cp templates/portal/services_here.html "templates/portal/services_here.html.bak.${STAMP}"
cat > templates/portal_base.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Portal - OutsideTheBox{% endblock %}</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/brand.css">
<link rel="icon" type="image/png" href="/static/favicon.png">
{% block head_extra %}{% endblock %}
</head>
<body>
{% include "includes/site_nav.html" %}
<div class="portal-shell">
<div class="portal-wrap">
{% block portal_content %}{% endblock %}
</div>
</div>
{% block scripts %}{% endblock %}
{% include "includes/otb_footer.html" %}
</body>
</html>
EOF
cat > templates/portal_dashboard.html <<'EOF'
{% extends "portal_base.html" %}
{% block title %}Client Dashboard - OutsideTheBox{% endblock %}
{% block portal_content %}
<div class="portal-page-header">
<div>
<h1 class="portal-page-title">Client Dashboard</h1>
<p class="portal-client-name">{{ client.company_name or client.contact_name or client.email }}</p>
<p class="portal-page-subtitle">Invoices, balances, and account activity in one place.</p>
</div>
<div class="portal-toolbar" style="display:flex;flex-direction:column;align-items:flex-end;gap:10px;">
<div style="display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;">
<a class="portal-btn primary" href="/portal/services">Services Here</a>
<a class="portal-btn primary" href="/portal/invoices/download-all">Download All Invoices</a>
<a class="portal-btn" href="mailto:support@outsidethebox.top?subject=Customer%20Support">Customer Support</a>
<a class="portal-btn" href="/portal/logout">Logout</a>
</div>
<div style="text-align:right;font-size:14px;opacity:0.95;">
<div>Logged in as: <strong>{{ client.contact_name or client.company_name or client.email }}</strong></div>
{% if client_credit_balance and client_credit_balance != "0.00" %}
<div style="margin-top:6px;">
<span style="display:inline-block;padding:4px 10px;border-radius:999px;background:#0f2f21;color:#86efac;font-weight:700;">
🏦 Credit: ${{ client_credit_balance }}
</span>
</div>
{% endif %}
</div>
</div>
</div>
<div class="summary-grid">
<div class="summary-card">
<h3>Total Invoices</h3>
<div class="summary-value">{{ invoice_count }}</div>
<div class="summary-sub">Invoices currently visible in your portal</div>
</div>
<div class="summary-card">
<h3>Total Outstanding</h3>
<div class="summary-value">{{ total_outstanding }}</div>
<div class="summary-sub">Current unpaid balance</div>
</div>
<div class="summary-card">
<h3>Total Paid</h3>
<div class="summary-value">{{ total_paid }}</div>
<div class="summary-sub">Payments already applied</div>
</div>
</div>
<h2 class="section-title">Invoices</h2>
<div class="table-card">
<table class="portal-table">
<thead>
<tr>
<th>Invoice</th>
<th>Status</th>
<th>Created</th>
<th>Total</th>
<th>Paid</th>
<th>Outstanding</th>
</tr>
</thead>
<tbody>
{% for row in invoices %}
<tr>
<td>
<a class="invoice-link" href="/portal/invoice/{{ row.id }}">
{{ row.invoice_number or ("INV-" ~ row.id) }}
</a>
</td>
<td>
{% set s = (row.status or "")|lower %}
{% if s == "paid" %}
<span class="status-badge status-paid">{{ row.status }}</span>
{% if row.payment_method_label %}
<div class="payment-method{% if row.payment_method_label == "Square" %} payment-square{% elif row.payment_method_label == "e-Transfer" %} payment-etransfer{% elif row.payment_method_label == "ETHO" %} payment-etho{% elif row.payment_method_label == "ETI" or row.payment_method_label == "EGAZ" %} payment-etica{% elif row.payment_method_label == "ALT" %} payment-alt{% elif row.payment_method_label == "CAD" %} payment-cad{% endif %}">
{{ row.payment_method_label }}
</div>
{% endif %}
{% elif s == "pending" %}
<span class="status-badge status-pending">{{ row.status }}</span>
{% elif s == "overdue" %}
<span class="status-badge status-overdue">{{ row.status }}</span>
{% else %}
<span class="status-badge status-other">{{ row.status }}</span>
{% endif %}
</td>
<td>{{ row.created_at }}</td>
<td>{{ row.total_amount }}</td>
<td>{{ row.amount_paid }}</td>
<td>{{ row.outstanding }}</td>
</tr>
{% else %}
<tr>
<td colspan="6">No invoices available.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
{% block scripts %}
<script>
(function() {
setTimeout(function() { window.location.reload(); }, 20000);
})();
</script>
{% endblock %}
EOF
cat > templates/portal/services_here.html <<'EOF'
{% extends "portal_base.html" %}
{% block title %}Services - OutsideTheBox{% endblock %}
{% block portal_content %}
<div class="portal-page-header">
<div>
<h1 class="portal-page-title">Services</h1>
<p class="portal-client-name">{{ client_name }}</p>
<p class="portal-page-subtitle">Launch available OTB services from one place.</p>
</div>
<div class="portal-toolbar" style="display:flex;flex-direction:column;align-items:flex-end;gap:10px;">
<div style="display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;">
<a class="portal-btn primary" href="/portal/dashboard">Back to Dashboard</a>
<a class="portal-btn" href="/portal/logout">Logout</a>
</div>
<div style="text-align:right;font-size:14px;opacity:0.95;">
<div>Logged in as: <strong>{{ client_name }}</strong></div>
</div>
</div>
</div>
<section class="services-grid">
{% for service in services %}
<article class="service-card status-{{ service.status }}">
<div class="service-card-header">
<div>
<h2>{{ service.name }}</h2>
<p>{{ service.summary }}</p>
</div>
<div>
{% if service.status == 'beta' %}
<span class="service-badge service-badge-beta">Beta</span>
{% elif service.status == 'coming_soon' %}
<span class="service-badge service-badge-soon">Coming Soon</span>
{% else %}
<span class="service-badge">Available</span>
{% endif %}
</div>
</div>
<div class="service-card-actions">
{% if service.enabled %}
<a class="portal-btn primary" href="{{ service.href }}">{{ service.button_text }}</a>
{% else %}
<button class="portal-btn btn-disabled" disabled>{{ service.button_text }}</button>
{% endif %}
</div>
</article>
{% endfor %}
</section>
<style>
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
.service-card {
border-radius: 18px;
padding: 22px;
border: 1px solid rgba(255,255,255,0.10);
background: rgba(8, 18, 37, 0.72);
box-shadow: 0 8px 24px rgba(0,0,0,0.18);
}
.service-card-header {
display: flex;
justify-content: space-between;
gap: 16px;
align-items: flex-start;
}
.service-card h2 {
margin: 0 0 10px 0;
font-size: 1.3rem;
}
.service-card p {
margin: 0;
line-height: 1.5;
opacity: 0.92;
}
.service-card-actions {
margin-top: 22px;
}
.service-badge {
display: inline-block;
padding: 6px 10px;
border-radius: 999px;
font-size: 0.82rem;
font-weight: 700;
background: rgba(255,255,255,0.10);
}
.service-badge-beta {
background: rgba(73, 192, 255, 0.18);
}
.service-badge-soon {
background: rgba(255, 196, 73, 0.18);
}
.btn-disabled {
opacity: 0.65;
cursor: not-allowed;
}
</style>
{% endblock %}
EOF
sudo systemctl restart otb_billing.service
sudo systemctl status otb_billing.service --no-pager -l | sed -n '1,30p'

78
patch6.sh

@ -1,78 +0,0 @@
cd /home/def/otb_billing || exit 1
cp backend/routes/portal_services.py backend/routes/portal_services.py.bak.$(date +%Y%m%d-%H%M%S)
cat > backend/routes/portal_services.py <<'EOF'
from flask import Blueprint, render_template, session, redirect, url_for, flash
portal_services_bp = Blueprint("portal_services", __name__)
def _portal_user_is_logged_in() -> bool:
return bool(
session.get("portal_user_id")
or session.get("client_user_id")
or session.get("portal_client_id")
or session.get("client_id")
or session.get("user_id")
)
@portal_services_bp.route("/portal/services")
def portal_services_home():
if not _portal_user_is_logged_in():
flash("Please sign in to access services.", "warning")
return redirect(url_for("portal_login"))
client = {
"contact_name": session.get("portal_contact_name"),
"company_name": session.get("portal_company_name"),
"email": session.get("portal_email"),
}
client_name = (
client.get("contact_name")
or client.get("company_name")
or client.get("email")
or "Client"
)
services = [
{
"key": "follow_me",
"name": "Follow-me Tracker",
"summary": "Create and manage your GPS tracking network. Free for up to 2 users.",
"status": "beta",
"enabled": True,
"href": "/follow-me",
"button_text": "Open Follow-me",
},
{
"key": "video_render",
"name": "Video Rendering / Streaming",
"summary": "Submit video rendering, conversion, and hosted streaming jobs.",
"status": "coming_soon",
"enabled": False,
"href": "#",
"button_text": "Coming Soon",
},
{
"key": "miner_rentals",
"name": "Miner Rentals",
"summary": "Rent available OTB hashpower by time or package.",
"status": "coming_soon",
"enabled": False,
"href": "#",
"button_text": "Coming Soon",
},
]
return render_template(
"portal/services_here.html",
client=client,
client_name=client_name,
services=services,
)
EOF
python3 -m py_compile backend/routes/portal_services.py
sudo systemctl restart otb_billing.service
sudo systemctl status otb_billing.service --no-pager -l | sed -n '1,30p'

157
patch7.sh

@ -1,157 +0,0 @@
cd /home/def/otb_billing || exit 1
set -e
STAMP="$(date +%Y%m%d-%H%M%S)"
NEW_VERSION="v0.6.0"
ZIP_NAME="otb_billing-${NEW_VERSION}.zip"
echo "===== backups ====="
cp VERSION "VERSION.bak.${STAMP}"
cp README.md "README.md.bak.${STAMP}"
cp PROJECT_STATE.md "PROJECT_STATE.md.bak.${STAMP}"
echo "===== version bump ====="
printf '%s\n' "${NEW_VERSION}" > VERSION
echo "===== update README + PROJECT_STATE ====="
python3 <<'PY'
from pathlib import Path
from datetime import datetime, timezone
root = Path("/home/def/otb_billing")
version = "v0.6.0"
stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
utc_stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
readme = root / "README.md"
project_state = root / "PROJECT_STATE.md"
readme_text = readme.read_text() if readme.exists() else ""
project_text = project_state.read_text() if project_state.exists() else ""
readme_entry = f"""## {version} - {stamp}
### Highlights
- Added authenticated `/portal/services` page as a new service hub inside the billing portal.
- Added modular route file `backend/routes/portal_services.py` instead of expanding the main app with another large inline route block.
- Added shared `templates/portal_base.html` layout using Jinja `{% extends %}` / block pattern.
- Converted portal pages to the shared base-template structure for cleaner reuse and consistent branding.
- Added branded service cards for:
- Follow-me Tracker
- Video Rendering / Streaming
- Miner Rentals
- Fixed portal services layout so it uses the same nav, footer, styling, and light/dark toggle behavior as the working portal/dashboard pages.
- Corrected the Follow-me service link to use the real external service domain.
- Updated service launch buttons so external services open in a new tab.
- Improved portal services identity display to prepare for showing real logged-in client identity instead of a generic placeholder.
### Notes
- This is the first successful rollout of the reusable `portal_base.html` structure and it should be used for future portal pages and future project builds.
- This release establishes the billing portal as the launch point for OTB services.
"""
project_entry = f"""# Project State Update - {version}
Updated: {utc_stamp}
## Current Version
{version}
## Current Status
OTB Billing has been advanced from a billing-only portal toward a unified service-launch platform.
## Completed This Session
- Created `/portal/services` authenticated service hub.
- Added modular route file: `backend/routes/portal_services.py`
- Added shared Jinja portal base template: `templates/portal_base.html`
- Converted:
- `templates/portal_dashboard.html`
- `templates/portal/services_here.html`
to the new shared base-template architecture.
- Restored consistent OTB branding, nav, footer, spacing, and dark/light toggle behavior on the services page.
- Added service cards for:
- Follow-me Tracker (active/beta)
- Video Rendering / Streaming (coming soon)
- Miner Rentals (coming soon)
- Fixed service routing so Follow-me points to the real service domain.
- Updated external service launch links to open in a new tab.
- Confirmed the new shared template architecture is successful and should be reused in future pages/projects.
## Architectural Direction
The portal now has a proven reusable pattern:
- `templates/portal_base.html`
- child templates using `{{% extends "portal_base.html" %}}`
- focused page content blocks instead of duplicating full HTML shell on each page
This is now the preferred portal/page structure going forward.
## Next Logical Steps
- Unify logged-in client identity handling across portal routes.
- Add real service-aware handoff / account linkage for Follow-me.
- Add future service cards/pages without duplicating layout shell.
- Move page-specific inline service-card CSS into shared stylesheet when ready.
"""
def prepend_once(existing: str, new_block: str, marker: str) -> str:
if marker in existing:
return existing
if existing.startswith("# "):
return existing.rstrip() + "\n\n" + new_block
return new_block + existing
readme.write_text(prepend_once(readme_text, readme_entry, f"## {version} - "))
project_state.write_text(prepend_once(project_text, project_entry, f"# Project State Update - {version}"))
PY
echo "===== build full zip snapshot ====="
rm -f "releases/${ZIP_NAME}"
mkdir -p releases
python3 <<'PY'
from pathlib import Path
import zipfile
root = Path("/home/def/otb_billing")
zip_path = root / "releases" / "otb_billing-v0.6.0.zip"
exclude_parts = {
".git",
"__pycache__",
".pytest_cache",
}
exclude_suffixes = {
".pyc",
".pyo",
}
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for path in root.rglob("*"):
rel = path.relative_to(root)
if any(part in exclude_parts for part in rel.parts):
continue
if path.suffix in exclude_suffixes:
continue
if path == zip_path:
continue
zf.write(path, arcname=str(Path("otb_billing-v0.6.0") / rel))
print(zip_path)
PY
echo "===== quick verify ====="
echo
echo "--- VERSION ---"
cat VERSION
echo
echo "--- README.md top 40 ---"
sed -n '1,40p' README.md
echo
echo "--- PROJECT_STATE.md top 60 ---"
sed -n '1,60p' PROJECT_STATE.md
echo
echo "--- ZIP snapshot ---"
ls -lh "releases/${ZIP_NAME}"
echo
echo "===== optional git review ====="
git status --short

125
patch8.sh

@ -1,125 +0,0 @@
cd /home/def/otb_billing || exit 1
set -e
STAMP="$(date +%Y%m%d-%H%M%S)"
NEW_VERSION="v0.6.0"
ZIP_NAME="otb_billing-${NEW_VERSION}.zip"
echo "===== backups ====="
cp VERSION "VERSION.bak.${STAMP}"
cp README.md "README.md.bak.${STAMP}"
cp PROJECT_STATE.md "PROJECT_STATE.md.bak.${STAMP}"
echo "===== version bump ====="
printf '%s\n' "${NEW_VERSION}" > VERSION
echo "===== update README + PROJECT_STATE ====="
python3 <<'PY'
from pathlib import Path
from datetime import datetime, timezone
root = Path("/home/def/otb_billing")
version = "v0.6.0"
stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
utc_stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
readme = root / "README.md"
project_state = root / "PROJECT_STATE.md"
readme_text = readme.read_text() if readme.exists() else ""
project_text = project_state.read_text() if project_state.exists() else ""
readme_entry = """## v0.6.0 - {stamp}
### Highlights
- Added authenticated /portal/services page as a service hub
- Introduced modular route backend/routes/portal_services.py
- Created shared templates/portal_base.html layout
- Converted portal pages to base-template architecture
- Added service cards (Follow-me, Video, Miner Rentals)
- Fixed branding, nav, footer, and toggle consistency
- Corrected Follow-me external link
- External services now open in new tabs
- Improved identity display for logged-in user
### Notes
- portal_base.html is now the standard structure for future pages and projects
- Billing portal is now the launch point for all OTB services
""".replace("{stamp}", stamp)
project_entry = """# Project State Update - v0.6.0
Updated: {utc_stamp}
## Current Version
v0.6.0
## Current Status
OTB Billing is now a service-launch platform, not just billing.
## Completed This Session
- Added /portal/services page
- Added portal_services.py route module
- Created portal_base.html shared template
- Converted dashboard + services page to shared layout
- Restored consistent branding, nav, footer, toggle
- Added service cards (Follow-me, Video, Miner)
- Fixed external service routing
- Enabled new-tab launch for services
## Architecture
Using shared base template:
templates/portal_base.html
All pages now:
{% extends "portal_base.html" %}
## Next Steps
- Unify client identity across all routes
- Add Follow-me provisioning + billing linkage
- Move inline CSS into shared styles later
""".replace("{utc_stamp}", utc_stamp)
def prepend(existing, new_block, marker):
if marker in existing:
return existing
if existing.startswith("# "):
return existing.rstrip() + "\n\n" + new_block
return new_block + existing
readme.write_text(prepend(readme_text, readme_entry, "## v0.6.0"))
project_state.write_text(prepend(project_text, project_entry, "# Project State Update - v0.6.0"))
PY
echo "===== build full zip snapshot ====="
rm -f "releases/${ZIP_NAME}"
mkdir -p releases
python3 <<'PY'
from pathlib import Path
import zipfile
root = Path("/home/def/otb_billing")
zip_path = root / "releases" / "otb_billing-v0.6.0.zip"
exclude_parts = {".git", "__pycache__", ".pytest_cache"}
exclude_suffixes = {".pyc", ".pyo"}
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for path in root.rglob("*"):
rel = path.relative_to(root)
if any(part in exclude_parts for part in rel.parts):
continue
if path.suffix in exclude_suffixes:
continue
if path == zip_path:
continue
zf.write(path, arcname=str(Path("otb_billing-v0.6.0") / rel))
print(zip_path)
PY
echo "===== verify ====="
cat VERSION
ls -lh "releases/${ZIP_NAME}"

27
static/css/brand.css

@ -547,3 +547,30 @@ html[data-theme="light"] .otb-dot{
margin-top:8px !important; margin-top:8px !important;
} }
} }
/* light theme button consistency fix */
body.light-theme .portal-btn,
body.light-theme a.portal-btn,
body.light-theme button.portal-btn {
background: rgba(255,255,255,0.75);
color: var(--text, #10233f);
border: 1px solid rgba(40,70,120,0.14);
opacity: 1;
}
body.light-theme .portal-btn.primary,
body.light-theme a.portal-btn.primary,
body.light-theme button.portal-btn.primary {
color: #081528;
}
body.light-theme .portal-toolbar .portal-btn,
body.light-theme .portal-actions .portal-btn {
opacity: 1;
}
body.light-theme .portal-btn[disabled],
body.light-theme button.portal-btn[disabled],
body.light-theme .portal-btn.disabled {
opacity: 0.55;
}

166
static/css/style.css

@ -1108,3 +1108,169 @@ body{
background: #2563eb; background: #2563eb;
color: #ffffff; color: #ffffff;
} }
/* ===== final portal light-theme button normalization ===== */
body.light-theme .portal-btn,
body.light-theme a.portal-btn,
body.light-theme button.portal-btn,
body.light-theme .btn,
body.light-theme a.btn,
body.light-theme button.btn {
background: rgba(255,255,255,0.88);
color: #10233f;
border: 1px solid rgba(40,70,120,0.14);
box-shadow: none;
opacity: 1 !important;
}
body.light-theme .portal-btn:hover,
body.light-theme a.portal-btn:hover,
body.light-theme button.portal-btn:hover,
body.light-theme .btn:hover,
body.light-theme a.btn:hover,
body.light-theme button.btn:hover {
border-color: rgba(122,162,255,.45);
box-shadow: 0 0 0 4px rgba(122,162,255,.10);
text-decoration: none;
}
body.light-theme .portal-btn.primary,
body.light-theme .btn.primary {
background: linear-gradient(135deg, rgba(122,162,255,.95), rgba(98,230,183,.85));
border-color: transparent;
color: #071017;
}
body.light-theme .portal-btn[disabled],
body.light-theme button.portal-btn[disabled],
body.light-theme .portal-btn.disabled,
body.light-theme .btn[disabled],
body.light-theme button.btn[disabled],
body.light-theme .btn.disabled {
opacity: 0.5 !important;
}
/* ===== portal button fix for actual OTB light theme selectors ===== */
html[data-theme="light"] .portal-btn,
html[data-theme="light"] a.portal-btn,
html[data-theme="light"] button.portal-btn,
body.otb-light .portal-btn,
body.otb-light a.portal-btn,
body.otb-light button.portal-btn,
html[data-theme="light"] .btn,
html[data-theme="light"] a.btn,
html[data-theme="light"] button.btn,
body.otb-light .btn,
body.otb-light a.btn,
body.otb-light button.btn {
background: rgba(255,255,255,0.88) !important;
color: #10233f !important;
border: 1px solid rgba(40,70,120,0.14) !important;
box-shadow: none !important;
opacity: 1 !important;
}
html[data-theme="light"] .portal-btn:hover,
html[data-theme="light"] a.portal-btn:hover,
html[data-theme="light"] button.portal-btn:hover,
body.otb-light .portal-btn:hover,
body.otb-light a.portal-btn:hover,
body.otb-light button.portal-btn:hover,
html[data-theme="light"] .btn:hover,
html[data-theme="light"] a.btn:hover,
html[data-theme="light"] button.btn:hover,
body.otb-light .btn:hover,
body.otb-light a.btn:hover,
body.otb-light button.btn:hover {
border-color: rgba(122,162,255,.45) !important;
box-shadow: 0 0 0 4px rgba(122,162,255,.10) !important;
text-decoration: none !important;
}
html[data-theme="light"] .portal-btn.primary,
html[data-theme="light"] .btn.primary,
body.otb-light .portal-btn.primary,
body.otb-light .btn.primary {
background: linear-gradient(135deg, rgba(122,162,255,.95), rgba(98,230,183,.85)) !important;
border-color: transparent !important;
color: #071017 !important;
}
html[data-theme="light"] .portal-btn[disabled],
html[data-theme="light"] button.portal-btn[disabled],
html[data-theme="light"] .portal-btn.disabled,
html[data-theme="light"] .btn[disabled],
html[data-theme="light"] button.btn[disabled],
html[data-theme="light"] .btn.disabled,
body.otb-light .portal-btn[disabled],
body.otb-light button.portal-btn[disabled],
body.otb-light .portal-btn.disabled,
body.otb-light .btn[disabled],
body.otb-light button.btn[disabled],
body.otb-light .btn.disabled {
opacity: 0.5 !important;
}
/* ===== portal button fix for actual OTB light theme selectors ===== */
html[data-theme="light"] .portal-btn,
html[data-theme="light"] a.portal-btn,
html[data-theme="light"] button.portal-btn,
body.otb-light .portal-btn,
body.otb-light a.portal-btn,
body.otb-light button.portal-btn,
html[data-theme="light"] .btn,
html[data-theme="light"] a.btn,
html[data-theme="light"] button.btn,
body.otb-light .btn,
body.otb-light a.btn,
body.otb-light button.btn {
background: rgba(255,255,255,0.88) !important;
color: #10233f !important;
border: 1px solid rgba(40,70,120,0.14) !important;
box-shadow: none !important;
opacity: 1 !important;
}
html[data-theme="light"] .portal-btn:hover,
html[data-theme="light"] a.portal-btn:hover,
html[data-theme="light"] button.portal-btn:hover,
body.otb-light .portal-btn:hover,
body.otb-light a.portal-btn:hover,
body.otb-light button.portal-btn:hover,
html[data-theme="light"] .btn:hover,
html[data-theme="light"] a.btn:hover,
html[data-theme="light"] button.btn:hover,
body.otb-light .btn:hover,
body.otb-light a.btn:hover,
body.otb-light button.btn:hover {
border-color: rgba(122,162,255,.45) !important;
box-shadow: 0 0 0 4px rgba(122,162,255,.10) !important;
text-decoration: none !important;
}
html[data-theme="light"] .portal-btn.primary,
html[data-theme="light"] .btn.primary,
body.otb-light .portal-btn.primary,
body.otb-light .btn.primary {
background: linear-gradient(135deg, rgba(122,162,255,.95), rgba(98,230,183,.85)) !important;
border-color: transparent !important;
color: #071017 !important;
}
html[data-theme="light"] .portal-btn[disabled],
html[data-theme="light"] button.portal-btn[disabled],
html[data-theme="light"] .portal-btn.disabled,
html[data-theme="light"] .btn[disabled],
html[data-theme="light"] button.btn[disabled],
html[data-theme="light"] .btn.disabled,
body.otb-light .portal-btn[disabled],
body.otb-light button.portal-btn[disabled],
body.otb-light .portal-btn.disabled,
body.otb-light .btn[disabled],
body.otb-light button.btn[disabled],
body.otb-light .btn.disabled {
opacity: 0.5 !important;
}

11
templatepatch.sh

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
echo "===== starting patch ====="
# commands directly here (no nested EOF)
# example:
echo "doing work..."
sleep 1
echo "===== patch completed successfully ====="

98
templates/service_templates/edit.html

@ -0,0 +1,98 @@
<!doctype html>
<html>
<head>
<title>Edit Service Template</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
</head>
<body>
<h1>Edit Service Template</h1>
<p><a href="/">Home</a></p>
<p><a href="/service-templates">Back to Service Templates</a></p>
{% if errors %}
<div style="border:1px solid red; padding:10px; margin-bottom:15px;">
<strong>Please fix the following:</strong>
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<form method="post">
<p>
Template Name *<br>
<input name="template_name" value="{{ template.template_name }}" required>
</p>
<p>
Service Type *<br>
<select name="service_type" required>
<option value="hosting" {% if template.service_type == 'hosting' %}selected{% endif %}>hosting</option>
<option value="rpc" {% if template.service_type == 'rpc' %}selected{% endif %}>rpc</option>
<option value="explorer" {% if template.service_type == 'explorer' %}selected{% endif %}>explorer</option>
<option value="node" {% if template.service_type == 'node' %}selected{% endif %}>node</option>
<option value="ipfs" {% if template.service_type == 'ipfs' %}selected{% endif %}>ipfs</option>
<option value="consulting" {% if template.service_type == 'consulting' %}selected{% endif %}>consulting</option>
<option value="crypto_infra" {% if template.service_type == 'crypto_infra' %}selected{% endif %}>Crypto Infra</option>
<option value="other" {% if template.service_type == 'other' %}selected{% endif %}>other</option>
</select>
</p>
<p>
Billing Cycle *<br>
<select name="billing_cycle" required>
<option value="one_time" {% if template.billing_cycle == 'one_time' %}selected{% endif %}>one_time</option>
<option value="monthly" {% if template.billing_cycle == 'monthly' %}selected{% endif %}>monthly</option>
<option value="quarterly" {% if template.billing_cycle == 'quarterly' %}selected{% endif %}>quarterly</option>
<option value="yearly" {% if template.billing_cycle == 'yearly' %}selected{% endif %}>yearly</option>
<option value="manual" {% if template.billing_cycle == 'manual' %}selected{% endif %}>manual</option>
</select>
</p>
<p>
Currency Code *<br>
<select name="currency_code" required>
<option value="CAD" {% if template.currency_code == 'CAD' %}selected{% endif %}>CAD</option>
<option value="ETHO" {% if template.currency_code == 'ETHO' %}selected{% endif %}>ETHO</option>
<option value="EGAZ" {% if template.currency_code == 'EGAZ' %}selected{% endif %}>EGAZ</option>
<option value="ALT" {% if template.currency_code == 'ALT' %}selected{% endif %}>ALT</option>
</select>
</p>
<p>
Recurring Amount *<br>
<input type="number" step="0.00000001" min="0" name="recurring_amount" value="{{ template.recurring_amount }}" required>
</p>
<p>
Setup Amount *<br>
<input type="number" step="0.00000001" min="0" name="setup_amount" value="{{ template.setup_amount }}" required>
</p>
<p>
Description<br>
<textarea name="description" rows="6" cols="70">{{ template.description or '' }}</textarea>
</p>
<p>
Active<br>
<select name="is_active">
<option value="1" {% if template.is_active %}selected{% endif %}>yes</option>
<option value="0" {% if not template.is_active %}selected{% endif %}>no</option>
</select>
</p>
<p>
<button type="submit">Save Service Template</button>
</p>
</form>
{% include "footer.html" %}
</body>
</html>

48
templates/service_templates/list.html

@ -0,0 +1,48 @@
<!doctype html>
<html>
<head>
<title>Service Templates</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
</head>
<body>
<h1>Service Templates</h1>
<p><a href="/">Home</a></p>
<p>
<a href="/services">Back to Services</a> |
<a href="/service-templates/new">Add Service Template</a>
</p>
<table border="1" cellpadding="6">
<tr>
<th>ID</th>
<th>Template Name</th>
<th>Type</th>
<th>Cycle</th>
<th>Currency</th>
<th>Recurring</th>
<th>Setup</th>
<th>Active</th>
<th>Actions</th>
</tr>
{% for t in templates %}
<tr>
<td>{{ t.id }}</td>
<td>{{ t.template_name }}</td>
<td>{{ t.service_type }}</td>
<td>{{ t.billing_cycle }}</td>
<td>{{ t.currency_code }}</td>
<td>{{ t.recurring_amount|money(t.currency_code) }}</td>
<td>{{ t.setup_amount|money(t.currency_code) }}</td>
<td>{% if t.is_active %}yes{% else %}no{% endif %}</td>
<td><a href="/service-templates/edit/{{ t.id }}">Edit</a></td>
</tr>
{% endfor %}
</table>
{% include "footer.html" %}
</body>
</html>

98
templates/service_templates/new.html

@ -0,0 +1,98 @@
<!doctype html>
<html>
<head>
<title>New Service Template</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
</head>
<body>
<h1>Add Service Template</h1>
<p><a href="/">Home</a></p>
<p><a href="/service-templates">Back to Service Templates</a></p>
{% if errors %}
<div style="border:1px solid red; padding:10px; margin-bottom:15px;">
<strong>Please fix the following:</strong>
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<form method="post">
<p>
Template Name *<br>
<input name="template_name" required>
</p>
<p>
Service Type *<br>
<select name="service_type" required>
<option value="hosting">hosting</option>
<option value="rpc">rpc</option>
<option value="explorer">explorer</option>
<option value="node">node</option>
<option value="ipfs">ipfs</option>
<option value="consulting">consulting</option>
<option value="crypto_infra">Crypto Infra</option>
<option value="other">other</option>
</select>
</p>
<p>
Billing Cycle *<br>
<select name="billing_cycle" required>
<option value="one_time">one_time</option>
<option value="monthly" selected>monthly</option>
<option value="quarterly">quarterly</option>
<option value="yearly">yearly</option>
<option value="manual">manual</option>
</select>
</p>
<p>
Currency Code *<br>
<select name="currency_code" required>
<option value="CAD" selected>CAD</option>
<option value="ETHO">ETHO</option>
<option value="EGAZ">EGAZ</option>
<option value="ALT">ALT</option>
</select>
</p>
<p>
Recurring Amount *<br>
<input type="number" step="0.00000001" min="0" name="recurring_amount" value="0.00000000" required>
</p>
<p>
Setup Amount *<br>
<input type="number" step="0.00000001" min="0" name="setup_amount" value="0.00000000" required>
</p>
<p>
Description<br>
<textarea name="description" rows="6" cols="70"></textarea>
</p>
<p>
Active<br>
<select name="is_active">
<option value="1" selected>yes</option>
<option value="0">no</option>
</select>
</p>
<p>
<button type="submit">Create Service Template</button>
</p>
</form>
{% include "footer.html" %}
</body>
</html>

47
templates/services/edit.html

@ -9,7 +9,10 @@
<h1>Edit Service</h1> <h1>Edit Service</h1>
<p><a href="/">Home</a></p> <p><a href="/">Home</a></p>
<p><a href="/services">Back to Services</a></p> <p>
<a href="/services">Back to Services</a> |
<a href="/service-templates">Service Templates</a>
</p>
{% if errors %} {% if errors %}
<div style="border:1px solid red; padding:10px; margin-bottom:15px;"> <div style="border:1px solid red; padding:10px; margin-bottom:15px;">
@ -41,6 +44,27 @@ Client *<br>
</select> </select>
</p> </p>
<p>
Load from Template<br>
<select id="template_select">
<option value="">-- select template --</option>
{% for t in templates %}
<option
value="{{ t.id }}"
data-name="{{ t.template_name }}"
data-type="{{ t.service_type }}"
data-cycle="{{ t.billing_cycle }}"
data-currency="{{ t.currency_code }}"
data-recurring="{{ t.recurring_amount }}"
data-setup="{{ t.setup_amount }}"
data-description="{{ (t.description or '')|e }}"
>
{{ t.template_name }} ({{ t.recurring_amount|money(t.currency_code) }}{% if t.setup_amount and t.setup_amount != 0 %}, setup {{ t.setup_amount|money(t.currency_code) }}{% endif %})
</option>
{% endfor %}
</select>
</p>
<p> <p>
Service Name *<br> Service Name *<br>
<input name="service_name" value="{{ service.service_name }}" required> <input name="service_name" value="{{ service.service_name }}" required>
@ -55,7 +79,7 @@ Service Type *<br>
<option value="node" {% if service.service_type == 'node' %}selected{% endif %}>node</option> <option value="node" {% if service.service_type == 'node' %}selected{% endif %}>node</option>
<option value="ipfs" {% if service.service_type == 'ipfs' %}selected{% endif %}>ipfs</option> <option value="ipfs" {% if service.service_type == 'ipfs' %}selected{% endif %}>ipfs</option>
<option value="consulting" {% if service.service_type == 'consulting' %}selected{% endif %}>consulting</option> <option value="consulting" {% if service.service_type == 'consulting' %}selected{% endif %}>consulting</option>
<option value="crypto_infra">Crypto Infra</option> <option value="crypto_infra" {% if service.service_type == 'crypto_infra' %}selected{% endif %}>Crypto Infra</option>
<option value="other" {% if service.service_type == 'other' %}selected{% endif %}>other</option> <option value="other" {% if service.service_type == 'other' %}selected{% endif %}>other</option>
</select> </select>
</p> </p>
@ -112,6 +136,25 @@ Description<br>
</form> </form>
<script>
(function() {
var select = document.getElementById("template_select");
if (!select) return;
select.addEventListener("change", function() {
var opt = this.options[this.selectedIndex];
if (!opt || !opt.dataset || !opt.dataset.name) return;
document.querySelector('[name="service_name"]').value = opt.dataset.name || '';
document.querySelector('[name="service_type"]').value = opt.dataset.type || 'other';
document.querySelector('[name="billing_cycle"]').value = opt.dataset.cycle || 'monthly';
document.querySelector('[name="currency_code"]').value = opt.dataset.currency || 'CAD';
document.querySelector('[name="recurring_amount"]').value = opt.dataset.recurring || '0.00000000';
document.querySelector('[name="description"]').value = opt.dataset.description || '';
});
})();
</script>
{% include "footer.html" %} {% include "footer.html" %}
</body> </body>
</html> </html>

5
templates/services/list.html

@ -9,7 +9,10 @@
<h1>Services</h1> <h1>Services</h1>
<p><a href="/">Home</a></p> <p><a href="/">Home</a></p>
<p><a href="/services/new">Add Service</a></p> <p>
<a href="/services/new">Add Service</a> |
<a href="/service-templates">Service Templates</a>
</p>
<table border="1" cellpadding="6"> <table border="1" cellpadding="6">
<tr> <tr>

48
templates/services/new.html

@ -8,6 +8,12 @@
<h1>Add Service</h1> <h1>Add Service</h1>
<p><a href="/">Home</a></p>
<p>
<a href="/services">Back to Services</a> |
<a href="/service-templates">Service Templates</a>
</p>
<form method="post"> <form method="post">
<p> <p>
@ -20,6 +26,27 @@ Client<br>
</select> </select>
</p> </p>
<p>
Load from Template<br>
<select id="template_select">
<option value="">-- select template --</option>
{% for t in templates %}
<option
value="{{ t.id }}"
data-name="{{ t.template_name }}"
data-type="{{ t.service_type }}"
data-cycle="{{ t.billing_cycle }}"
data-currency="{{ t.currency_code }}"
data-recurring="{{ t.recurring_amount }}"
data-setup="{{ t.setup_amount }}"
data-description="{{ (t.description or '')|e }}"
>
{{ t.template_name }} ({{ t.recurring_amount|money(t.currency_code) }}{% if t.setup_amount and t.setup_amount != 0 %}, setup {{ t.setup_amount|money(t.currency_code) }}{% endif %})
</option>
{% endfor %}
</select>
</p>
<p> <p>
Service Name<br> Service Name<br>
<input name="service_name" required> <input name="service_name" required>
@ -91,6 +118,25 @@ Description<br>
</form> </form>
<script>
(function() {
var select = document.getElementById("template_select");
if (!select) return;
select.addEventListener("change", function() {
var opt = this.options[this.selectedIndex];
if (!opt || !opt.dataset || !opt.dataset.name) return;
document.querySelector('[name="service_name"]').value = opt.dataset.name || '';
document.querySelector('[name="service_type"]').value = opt.dataset.type || 'other';
document.querySelector('[name="billing_cycle"]').value = opt.dataset.cycle || 'monthly';
document.querySelector('[name="currency_code"]').value = opt.dataset.currency || 'CAD';
document.querySelector('[name="recurring_amount"]').value = opt.dataset.recurring || '0.00000000';
document.querySelector('[name="description"]').value = opt.dataset.description || '';
});
})();
</script>
{% include "footer.html" %}
</body> </body>
</html> </html>
{% include "footer.html" %}

Loading…
Cancel
Save