| Server IP : 3.147.158.171 / Your IP : 216.73.216.229 Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5 System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64 User : ec2-user ( 1000) PHP Version : 8.4.21 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /tsai/repo/api/scripts/ |
Upload File : |
"""
Article Schedule Report
Generates a report of article scheduling for all characters across all sites
in a given environment. Shows each character's calendar schedule and
identifies characters that will never post (no schedule).
Usage:
cd api
python scripts/article_schedule_report.py [--env development|production|scike_ai]
The TSAI_ENV environment variable is used by default. Pass --env to override.
"""
import argparse
import os
import sys
from datetime import datetime
from pathlib import Path
# Parse --env BEFORE any app imports so TSAI_ENV is set correctly
parser = argparse.ArgumentParser(description='Article schedule report for all characters')
parser.add_argument(
'--env',
choices=['development', 'production', 'scike_ai'],
default=os.getenv('TSAI_ENV', 'development'),
help='Environment to report on (default: TSAI_ENV or development)'
)
args = parser.parse_args()
os.environ['TSAI_ENV'] = args.env
# Load .env before app imports
from dotenv import load_dotenv
API_DIR = Path(__file__).resolve().parent.parent
env_path = API_DIR / '.env'
load_dotenv(dotenv_path=env_path)
# Re-apply --env after dotenv (which may set TSAI_ENV to something else)
os.environ['TSAI_ENV'] = args.env
# Add api directory to path so app modules can be imported
sys.path.insert(0, str(API_DIR))
# Now safe to import app modules
from app.services.site_config_service import SiteConfigService
from app.services.drupal import DrupalService
from app.appTypes import Character, ContentSchedule
DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
ENV_LABELS = {
'development': 'Development (local)',
'production': 'TSAI Server (turmansolutions.ai)',
'scike_ai': 'Scike Server (scike.ai)',
}
def format_days_of_week(days):
"""Convert JS day numbers to readable names."""
if not days:
return '-'
return ', '.join(DAY_NAMES[d] for d in sorted(days) if 0 <= d <= 6)
def format_days_of_month(days):
"""Format day-of-month list."""
if not days:
return '-'
return ', '.join(str(d) for d in sorted(days))
def format_hours(hours):
"""Format preferred hours list."""
if not hours:
return 'any'
return ', '.join(f'{h}:00' for h in sorted(hours))
def has_day_constraints(schedule):
"""True if the schedule has at least one matching day constraint."""
return bool(schedule and (schedule.daysOfWeek or schedule.daysOfMonth))
def is_active_for_articles(character):
"""Check if character is actively configured to produce articles."""
if not character.enabled:
return False
return has_day_constraints(character.articleSchedule)
def get_inactive_reason(character):
"""Return reason a character is inactive for articles."""
if not character.enabled:
return 'disabled'
return 'no article schedule'
def print_character_row(char):
"""Print a single character's article schedule info."""
sched = char.articleSchedule
sw = ' [system-wide]' if char.systemWide else ''
print(f' {char.name}{sw}')
dow = format_days_of_week(sched.daysOfWeek) if sched else '-'
dom = format_days_of_month(sched.daysOfMonth) if sched else '-'
hrs = format_hours(sched.preferredHours) if sched else 'any'
print(f' days of week: {dow}')
print(f' days of month: {dom}')
print(f' hours: {hrs}')
print()
def main():
env = args.env
now = datetime.now()
print('=' * 70)
print(f' ARTICLE SCHEDULE REPORT')
print(f' Environment: {ENV_LABELS.get(env, env)}')
print(f' Source: Drupal site_config (via SiteConfigService)')
print(f' Generated: {now.strftime("%Y-%m-%d %H:%M:%S")}')
print('=' * 70)
print()
site_config_service = SiteConfigService()
drupal_service = DrupalService()
sites = site_config_service.get_all_sites()
# Fetch system-wide characters once
system_wide_data = drupal_service.fetch_system_wide_characters()
# Track unique drupal_target_ids to avoid re-fetching the same characters
fetched_target_ids = {}
# Track all characters across sites for summary
all_active = []
all_inactive = []
total_sites = 0
for site in sites:
total_sites += 1
print('-' * 70)
print(f' Site: {site.slug}')
print(f' Host: {site.wp_host}')
print('-' * 70)
if not site.drupal_target_id:
print(' (no drupal_target_id configured — skipping character fetch)')
print()
continue
# Fetch or reuse characters for this drupal_target_id
tid = site.drupal_target_id
if tid not in fetched_target_ids:
character_data = drupal_service.fetch_characters_by_uid(tid)
fetched_target_ids[tid] = character_data
else:
character_data = fetched_target_ids[tid]
# Combine with system-wide characters and parse
combined_data = character_data + system_wide_data
characters = drupal_service.create_characters(site, combined_data)
active = [c for c in characters if is_active_for_articles(c)]
inactive = [c for c in characters if not is_active_for_articles(c)]
if not active:
print(' No active article-posting characters.')
print()
else:
print(f' Active characters ({len(active)}):')
print()
for char in sorted(active, key=lambda c: c.name):
print_character_row(char)
all_active.extend(active)
all_inactive.extend(inactive)
# Summary
print('=' * 70)
print(f' SUMMARY')
print('=' * 70)
print(f' Sites: {total_sites}')
print(f' Active characters: {len(all_active)} (across all sites, may include duplicates)')
print(f' Inactive characters: {len(all_inactive)} (across all sites)')
print()
if all_inactive:
# Deduplicate by storageId for the summary
seen = set()
unique_inactive = []
for c in all_inactive:
key = c.storageId or c.name
if key not in seen:
seen.add(key)
unique_inactive.append(c)
print(f' Inactive characters (unique: {len(unique_inactive)}):')
for c in sorted(unique_inactive, key=lambda c: c.name):
reason = get_inactive_reason(c)
site_info = f' (site: {c.siteId})' if c.siteId else ''
sw = ' [system-wide]' if c.systemWide else ''
print(f' - {c.name}{sw}{site_info}: {reason}')
print()
if __name__ == '__main__':
main()