Add LGL Team migration with recursive duplicate detection

- Add wrike_monitor_lgl.py for LGL Team board
  * Updated API token and space configuration
  * All 11 custom fields mapped correctly
  * OMG# field now stores HTML links (matching LGL Team format)
  * Recursive duplicate detection across entire Business Areas folder
  * Handles HTML link extraction for accurate comparison

- Add discover_board_info.py
  * Automated discovery tool for board configuration
  * Finds space IDs, custom field IDs, and item types
  * Generates configuration snippets

- Add config_lgl_team.py
  * Reference configuration for LGL Team space
  * Complete field mapping documentation

- Add test_duplicate_detection.py
  * Testing tool to verify duplicate detection logic
  * Can search for specific OMG# values

- Update requirements.txt

- Remove wrike_import.py (moved to OLD/)

Key Features:
- NO DUPLICATES: Searches entire Business Areas folder before creating
- HTML Link Support: OMG# stored as clickable links matching existing format
- Global Search: Uses descendants=true for efficient recursive search
- Format Matching: Generates OMG# links identical to existing entries

🤖 Generated with Claude Code
This commit is contained in:
Dave Porter 2025-12-17 15:19:17 -05:00
parent 9f2b54f864
commit a0cfe4b796
6 changed files with 1453 additions and 489 deletions

20
config_lgl_team.py Normal file
View file

@ -0,0 +1,20 @@
# === WRIKE SPACE CONFIGURATION - LGL team ===
WRIKE_SPACE_ID = "MQAAAABoHcTY"
WRIKE_SPACE_NAME = "LGL team"
DELIVERABLE_ITEM_TYPE_ID = "XXXXXX" # ⚠️ NOT FOUND - Update manually
# Custom field IDs
CUSTOM_FIELDS = {
"budget": "IEAGUQQ3JUAJCJ4N",
"impact": "IEAGUQQ3JUAJRZ7Q",
"notes": "IEAGUQQ3JUAKAJIW",
"rag": "IEAGUQQ3JUAJRZ7S",
"deliverable_category": "IEAGUQQ3JUAJLKMT",
"actions": "IEAGUQQ3JUAJRZ7W",
"shoot_date": "IEAGUQQ3JUAJRZ7X",
"omg_number": "XXXXXX", # ⚠️ NOT FOUND - Update manually
"omg_url": "IEAGUQQ3JUAKGGLP",
"box_link": "IEAGUQQ3JUAJRZ7Z",
"owner": "IEAGUQQ3JUAKAJIV",
}

229
discover_board_info.py Normal file
View file

@ -0,0 +1,229 @@
#!/usr/bin/env python3
"""
Wrike Board Discovery Script
Discovers all necessary IDs and configuration for a Wrike board/space
to configure wrike_monitor.py for a new environment
"""
import json
import requests
from typing import Dict, Optional, Any
class WrikeBoardDiscovery:
"""Discover Wrike board configuration"""
def __init__(self, api_token: str):
self.api_base = "https://www.wrike.com/api/v4"
self.api_token = api_token
self.headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
def make_request(self, endpoint: str) -> Optional[Dict[str, Any]]:
"""Make a GET request to Wrike API"""
url = f"{self.api_base}{endpoint}"
try:
response = requests.get(url, headers=self.headers, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"❌ API Error: {e}")
return None
def get_all_spaces(self):
"""Get all available spaces"""
print("\n" + "="*80)
print("DISCOVERING SPACES")
print("="*80)
result = self.make_request("/spaces")
if result and "data" in result:
spaces = result["data"]
print(f"\nFound {len(spaces)} space(s):\n")
for space in spaces:
print(f"{space['title']}")
print(f" ID: {space['id']}")
print(f" Access Type: {space.get('accessType', 'N/A')}")
print()
return spaces
return []
def get_space_by_name(self, space_name: str) -> Optional[str]:
"""Get space ID by name"""
result = self.make_request("/spaces")
if result and "data" in result:
for space in result["data"]:
if space["title"].lower() == space_name.lower():
return space["id"]
return None
def get_custom_item_types(self, space_id: str):
"""Get custom item types (like 'Deliverable')"""
print("\n" + "="*80)
print("DISCOVERING CUSTOM ITEM TYPES")
print("="*80)
result = self.make_request(f"/spaces/{space_id}/item_types")
if result and "data" in result:
item_types = result["data"]
print(f"\nFound {len(item_types)} custom item type(s):\n")
for item_type in item_types:
print(f"{item_type['title']}")
print(f" ID: {item_type['id']}")
print(f" Type: {item_type.get('type', 'N/A')}")
print()
return item_types
return []
def get_custom_fields(self):
"""Get all custom fields"""
print("\n" + "="*80)
print("DISCOVERING CUSTOM FIELDS")
print("="*80)
result = self.make_request("/customfields")
if result and "data" in result:
custom_fields = result["data"]
print(f"\nFound {len(custom_fields)} custom field(s):\n")
# Group by account/space
for field in custom_fields:
print(f"{field['title']}")
print(f" ID: {field['id']}")
print(f" Type: {field['type']}")
if 'accountId' in field:
print(f" Account ID: {field['accountId']}")
if 'spaceId' in field:
print(f" Space ID: {field['spaceId']}")
print()
return custom_fields
return []
def generate_config_snippet(self, space_name: str, space_id: str,
deliverable_type_id: Optional[str],
custom_fields: list):
"""Generate Python config snippet for the discovered board"""
print("\n" + "="*80)
print("CONFIGURATION SNIPPET FOR wrike_monitor.py")
print("="*80)
config = f'''
# === WRIKE SPACE CONFIGURATION - {space_name} ===
WRIKE_SPACE_ID = "{space_id}"
WRIKE_SPACE_NAME = "{space_name}"
'''
if deliverable_type_id:
config += f'DELIVERABLE_ITEM_TYPE_ID = "{deliverable_type_id}" # Custom item type "Deliverable"\n'
else:
config += 'DELIVERABLE_ITEM_TYPE_ID = "XXXXXX" # ⚠️ NOT FOUND - Update manually\n'
config += '\n# Custom field IDs\n'
config += 'CUSTOM_FIELDS = {\n'
# Try to match custom fields by name
field_mapping = {
"budget": None,
"impact": None,
"notes": None,
"rag": None,
"deliverable_category": None,
"actions": None,
"shoot_date": None,
"omg_number": None,
"omg_url": None,
"box_link": None,
"owner": None
}
for field in custom_fields:
field_title = field['title'].lower().replace(" ", "_").replace("#", "number")
for key in field_mapping.keys():
if key in field_title or field_title in key:
field_mapping[key] = field['id']
break
for key, value in field_mapping.items():
if value:
config += f' "{key}": "{value}",\n'
else:
config += f' "{key}": "XXXXXX", # ⚠️ NOT FOUND - Update manually\n'
config += '}\n'
print(config)
# Save to file
output_file = f"config_{space_name.replace(' ', '_').lower()}.py"
with open(output_file, 'w') as f:
f.write(config)
print(f"\n✅ Configuration saved to: {output_file}")
return config
def discover_board(self, space_name: str):
"""Main discovery function for a specific board"""
print(f"\n🔍 Discovering configuration for: {space_name}\n")
# Get all spaces
spaces = self.get_all_spaces()
# Find target space
space_id = self.get_space_by_name(space_name)
if not space_id:
print(f"\n❌ Space '{space_name}' not found!")
print(f"Available spaces: {[s['title'] for s in spaces]}")
return
print(f"\n✅ Found space: {space_name} (ID: {space_id})")
# Get custom item types
item_types = self.get_custom_item_types(space_id)
deliverable_type_id = None
for item_type in item_types:
if "deliverable" in item_type['title'].lower():
deliverable_type_id = item_type['id']
print(f"✅ Found 'Deliverable' custom item type: {deliverable_type_id}")
break
if not deliverable_type_id:
print("⚠️ WARNING: No 'Deliverable' custom item type found!")
# Get custom fields
custom_fields = self.get_custom_fields()
# Generate config
self.generate_config_snippet(space_name, space_id, deliverable_type_id, custom_fields)
print("\n" + "="*80)
print("DISCOVERY COMPLETE")
print("="*80)
print("\n📋 Next Steps:")
print(" 1. Review the generated config file")
print(" 2. Update any fields marked with ⚠️ XXXXXX")
print(" 3. Copy the configuration into wrike_monitor.py")
print(" 4. Test with a sample JSON file\n")
def main():
"""Main entry point"""
print("""
WRIKE BOARD DISCOVERY TOOL
Discovers IDs and configuration for Wrike monitor script
""")
# Configuration
API_TOKEN = "eyJ0dCI6InAiLCJhbGciOiJIUzI1NiIsInR2IjoiMiJ9.eyJkIjoie1wiYVwiOjY5NjM3MzksXCJpXCI6OTU5MTM2MSxcImNcIjo0NzAyNjA1LFwidVwiOjIzMjcyNDA0LFwiclwiOlwiVVNcIixcInNcIjpbXCJXXCIsXCJGXCIsXCJJXCIsXCJVXCIsXCJLXCIsXCJDXCIsXCJEXCIsXCJNXCIsXCJBXCIsXCJMXCIsXCJQXCJdLFwielwiOltdLFwidFwiOjB9IiwiaWF0IjoxNzY2MDAwNjMyfQ.FwtNCIiBUbb82MlEnI_Z3vKt-W8IgKRwLwZY6IY6fEI"
TARGET_SPACE = "LGL team" # Change this to discover a different space
discoverer = WrikeBoardDiscovery(API_TOKEN)
discoverer.discover_board(TARGET_SPACE)
if __name__ == "__main__":
main()

View file

@ -1,4 +1,4 @@
# Core dependencies (required for both scripts)
pip# Core dependencies (required for both scripts)
requests>=2.31.0
# Additional dependencies for wrike_monitor.py only

139
test_duplicate_detection.py Normal file
View file

@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
Test script to verify duplicate detection works correctly
This will check if a deliverable with a specific OMG# exists in the LGL Team board
"""
import requests
# Configuration
API_TOKEN = "eyJ0dCI6InAiLCJhbGciOiJIUzI1NiIsInR2IjoiMiJ9.eyJkIjoie1wiYVwiOjY5NjM3MzksXCJpXCI6OTU5MTM2MSxcImNcIjo0NzAyNjA1LFwidVwiOjIzMjcyNDA0LFwiclwiOlwiVVNcIixcInNcIjpbXCJXXCIsXCJGXCIsXCJJXCIsXCJVXCIsXCJLXCIsXCJDXCIsXCJEXCIsXCJNXCIsXCJBXCIsXCJMXCIsXCJQXCJdLFwielwiOltdLFwidFwiOjB9IiwiaWF0IjoxNzY2MDAwNjMyfQ.FwtNCIiBUbb82MlEnI_Z3vKt-W8IgKRwLwZY6IY6fEI"
API_BASE = "https://www.wrike.com/api/v4"
SPACE_ID = "MQAAAABoHcTY" # LGL Team
OMG_NUMBER_FIELD_ID = "IEAGUQQ3JUAJL7YF"
def make_request(endpoint):
"""Make a GET request to Wrike API"""
url = f"{API_BASE}{endpoint}"
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
try:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"❌ API Error: {e}")
return None
def find_deliverable_by_omg(folder_id, omg_number):
"""Find deliverable with matching OMG number"""
print(f"\n🔍 Searching for deliverable with OMG# {omg_number} in folder {folder_id}...")
# Get all subfolders/projects
result = make_request(f"/folders/{folder_id}/folders")
if result and "data" in result:
# Find parent folder with childIds
parent_folder = None
for item in result["data"]:
if item["id"] == folder_id:
parent_folder = item
break
if parent_folder:
child_ids = parent_folder.get("childIds", [])
print(f" Found {len(child_ids)} child items to check")
# For each child, get its details with custom fields
for child_id in child_ids:
child_result = make_request(f"/folders/{child_id}")
if child_result and "data" in child_result and len(child_result["data"]) > 0:
child = child_result["data"][0]
# Check if it's a project (deliverable)
if "project" in child:
custom_fields = child.get("customFields", [])
for field in custom_fields:
if field.get("id") == OMG_NUMBER_FIELD_ID:
field_value = field.get("value")
if field_value == omg_number:
print(f" ✅ FOUND: '{child['title']}' (ID: {child_id}) has OMG# {field_value}")
return child_id
else:
print(f" - '{child['title']}' has OMG# {field_value} (doesn't match)")
print(f" ❌ NOT FOUND: No deliverable with OMG# {omg_number}")
return None
def list_recent_deliverables_with_omg(space_id):
"""List some existing deliverables with their OMG numbers"""
print(f"\n📋 Listing existing deliverables with OMG# in space {space_id}...")
print("="*80)
# Get top-level folders in space
result = make_request(f"/folders/{space_id}/folders")
if result and "data" in result:
# Find space folder with childIds
space_folder = None
for item in result["data"]:
if item["id"] == space_id:
space_folder = item
break
if space_folder:
child_ids = space_folder.get("childIds", [])[:5] # Limit to first 5 folders
for folder_id in child_ids:
folder_result = make_request(f"/folders/{folder_id}/folders")
if folder_result and "data" in folder_result:
for item in folder_result["data"]:
if "project" in item:
custom_fields = item.get("customFields", [])
for field in custom_fields:
if field.get("id") == OMG_NUMBER_FIELD_ID:
omg_value = field.get("value", "N/A")
print(f"{item['title']}")
print(f" OMG#: {omg_value}")
print(f" ID: {item['id']}")
print()
def main():
print("""
DUPLICATE DETECTION TEST
Verifies that the script can find existing deliverables
""")
# List some existing deliverables
list_recent_deliverables_with_omg(SPACE_ID)
# Test duplicate detection
print("\n" + "="*80)
print("TESTING DUPLICATE DETECTION")
print("="*80)
# You can test with a known OMG# from the list above
test_omg = input("\nEnter an OMG# to test (or press Enter to skip): ").strip()
if test_omg:
# You'll need to provide a folder ID to search in
test_folder = input("Enter the folder/project ID to search in: ").strip()
if test_folder:
result = find_deliverable_by_omg(test_folder, test_omg)
if result:
print(f"\n✅ SUCCESS: Duplicate detection would catch this!")
print(f" The script would SKIP creating a new deliverable")
print(f" and use existing ID: {result}")
else:
print(f"\n✅ SUCCESS: OMG# {test_omg} not found")
print(f" The script would CREATE a new deliverable")
if __name__ == "__main__":
main()

View file

@ -1,488 +0,0 @@
#!/usr/bin/env python3
"""
Wrike Import Script
Processes JSON files to create folder structures, projects, and deliverable tasks in Wrike.
"""
import json
import os
import sys
import requests
import shutil
import time
from datetime import datetime, timedelta
from pathlib import Path
# Configuration
WRIKE_API_BASE = "https://www.wrike.com/api/v4"
WRIKE_TOKEN = "eyJ0dCI6InAiLCJhbGciOiJIUzI1NiIsInR2IjoiMiJ9.eyJkIjoie1wiYVwiOjY5NzM0OTgsXCJpXCI6OTUyOTY3MCxcImNcIjo0Njk5NTI3LFwidVwiOjIzMjcyNDA0LFwiclwiOlwiVVNcIixcInNcIjpbXCJXXCIsXCJGXCIsXCJJXCIsXCJVXCIsXCJLXCIsXCJDXCIsXCJEXCIsXCJNXCIsXCJBXCIsXCJMXCIsXCJQXCJdLFwielwiOltdLFwidFwiOjB9IiwiaWF0IjoxNzYwMDI4ODIzfQ.9f4t15LycpoH-NlzQC3s1K19fVqnAwcahG2D-J5E8dg"
STAGING_SPACE_ID = "MQAAAABpz7l_"
# Custom field IDs in Staging space
CUSTOM_FIELDS = {
"budget": "IEAGU2B2JUAJRZ7P",
"impact": "IEAGU2B2JUAJRZ7Q",
"notes": "IEAGU2B2JUAJRZ7R",
"rag": "IEAGU2B2JUAJRZ7S",
"deliverable_category": "IEAGU2B2JUAJRZ7T",
"actions": "IEAGU2B2JUAJRZ7W",
"shoot_date": "IEAGU2B2JUAJRZ7X",
"omg_number": "IEAGU2B2JUAJRZ7Y",
"box_link": "IEAGU2B2JUAJRZ7Z",
"owner": "IEAGU2B2JUAJRZ72"
}
# Cache for folder/project IDs
folder_cache = {}
project_cache = {}
def make_wrike_request(method, endpoint, data=None):
"""Make a request to the Wrike API."""
url = f"{WRIKE_API_BASE}{endpoint}"
headers = {
"Authorization": f"Bearer {WRIKE_TOKEN}",
"Content-Type": "application/json"
}
try:
if method == "GET":
response = requests.get(url, headers=headers)
elif method == "POST":
response = requests.post(url, headers=headers, json=data)
elif method == "PUT":
response = requests.put(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error making Wrike request: {e}")
if hasattr(e.response, 'text'):
print(f"Response: {e.response.text}")
return None
def parse_business_area(business_area):
"""Extract the folder name from BusinessArea string.
Example: 'BISSELL > PRODUCT MARKETING > Dry Specialty' -> 'Dry Specialty'
"""
parts = business_area.split(">")
if len(parts) > 0:
return parts[-1].strip()
return business_area.strip()
def parse_date(date_string):
"""Convert date string to YYYY-MM-DD format."""
if not date_string:
return None
try:
# Parse format like "2025-05-23 16:00:00+00"
dt = datetime.strptime(date_string.split('+')[0].strip(), "%Y-%m-%d %H:%M:%S")
return dt.strftime("%Y-%m-%d")
except:
return None
def get_or_create_folder(folder_name, parent_id=STAGING_SPACE_ID):
"""Get existing folder or create new one."""
# Check cache
cache_key = f"{parent_id}:{folder_name}"
if cache_key in folder_cache:
print(f" Found folder '{folder_name}' in cache: {folder_cache[cache_key]}")
return folder_cache[cache_key]
# Get folders in parent
result = make_wrike_request("GET", f"/folders/{parent_id}/folders")
if result and "data" in result:
for folder in result["data"]:
if folder["title"] == folder_name:
folder_id = folder["id"]
folder_cache[cache_key] = folder_id
print(f" Found existing folder '{folder_name}': {folder_id}")
return folder_id
# Create new folder
print(f" Creating new folder '{folder_name}' under {parent_id}")
data = {
"title": folder_name,
"description": f"{folder_name} category folder"
}
result = make_wrike_request("POST", f"/folders/{parent_id}/folders", data)
if result and "data" in result and len(result["data"]) > 0:
folder_id = result["data"][0]["id"]
folder_cache[cache_key] = folder_id
print(f" Created folder '{folder_name}': {folder_id}")
return folder_id
return None
def get_or_create_project(project_title, folder_id, project_details, campaign_code):
"""Get existing project or create new one."""
# Check cache
cache_key = f"{folder_id}:{project_title}"
if cache_key in project_cache:
print(f" Found project '{project_title}' in cache: {project_cache[cache_key]}")
return project_cache[cache_key]
# Get folders in parent
result = make_wrike_request("GET", f"/folders/{folder_id}/folders")
if result and "data" in result:
for item in result["data"]:
if item["title"] == project_title and "project" in item:
project_id = item["id"]
project_cache[cache_key] = project_id
print(f" Found existing project '{project_title}': {project_id}")
return project_id
# Create new project (as folder first, then convert)
print(f" Creating new project '{project_title}' under folder {folder_id}")
# Extract description (remove HTML tags)
description = project_details.get("Description", "")
if description:
description = description.replace("<p>", "").replace("</p>", "")
data = {
"title": project_title,
"description": description
}
result = make_wrike_request("POST", f"/folders/{folder_id}/folders", data)
if result and "data" in result and len(result["data"]) > 0:
project_id = result["data"][0]["id"]
# Convert to project with dates and custom fields
start_date = parse_date(project_details.get("StartDate"))
end_date = parse_date(project_details.get("EndDate"))
project_data = {"project": {}}
if start_date:
project_data["project"]["startDate"] = start_date
if end_date:
project_data["project"]["endDate"] = end_date
# Add campaign code as OMG # custom field for the project
if campaign_code:
project_data["customFields"] = [
{
"id": CUSTOM_FIELDS["omg_number"],
"value": campaign_code
}
]
result = make_wrike_request("PUT", f"/folders/{project_id}", project_data)
if result:
project_cache[cache_key] = project_id
print(f" Created project '{project_title}': {project_id} (Campaign Code: {campaign_code})")
return project_id
return None
def find_task_by_omg_number(project_id, omg_number):
"""Find a task in the project with the matching OMG number."""
if not omg_number:
return None
# Get all tasks in the project with custom fields
# IMPORTANT: Must add fields parameter to get custom field data
result = make_wrike_request("GET", f"/folders/{project_id}/tasks?fields=[\"customFields\"]")
if result and "data" in result:
for task in result["data"]:
# Check custom fields for matching OMG number
custom_fields = task.get("customFields", [])
for field in custom_fields:
if field.get("id") == CUSTOM_FIELDS["omg_number"]:
if field.get("value") == omg_number:
return task["id"]
return None
def create_or_update_deliverable_task(project_id, job_details):
"""Create or update a deliverable task in the project."""
task_title = job_details.get("Title", "Untitled Task")
job_number = job_details.get("Number", "")
# OMG number for deliverable is just the job number
omg_number = job_number
# Check if task already exists with this OMG number
existing_task_id = None
if omg_number:
print(f" Checking for existing task with OMG #: {omg_number}")
existing_task_id = find_task_by_omg_number(project_id, omg_number)
if existing_task_id:
print(f" Found existing task: {existing_task_id}")
print(f" ⊙ Task '{task_title}' already exists (Job #{job_number}) - skipping")
return existing_task_id # Return existing ID to mark as success
else:
print(f" Creating new task '{task_title}' (Job #{job_number})")
# Parse dates
due_date = parse_date(job_details.get("DueDate"))
brief_date = parse_date(job_details.get("BriefDate"))
# Build task data
task_data = {
"title": task_title,
"description": job_details.get("Notes", ""),
}
# Add dates
if due_date:
task_data["dates"] = {"due": due_date}
if brief_date:
task_data["dates"]["start"] = brief_date
# Add custom fields
custom_fields = []
# Deliverable Category (from JobCategory or MediaType)
job_category = job_details.get("JobCategory", job_details.get("MediaType", ""))
if job_category:
custom_fields.append({
"id": CUSTOM_FIELDS["deliverable_category"],
"value": job_category
})
# OMG Number (Job Number only)
if omg_number:
custom_fields.append({
"id": CUSTOM_FIELDS["omg_number"],
"value": omg_number
})
# Notes (combine type and details)
notes_parts = []
if job_details.get("Type"):
notes_parts.append(f"Type: {job_details['Type']}")
if job_details.get("Details"):
notes_parts.append(job_details["Details"])
if notes_parts:
custom_fields.append({
"id": CUSTOM_FIELDS["notes"],
"value": " | ".join(notes_parts)
})
if custom_fields:
task_data["customFields"] = custom_fields
# Create new task (without custom fields first)
basic_task_data = {
"title": task_title,
"description": job_details.get("Notes", ""),
}
# Add dates
if due_date:
basic_task_data["dates"] = {"due": due_date}
if brief_date:
basic_task_data["dates"]["start"] = brief_date
result = make_wrike_request("POST", f"/folders/{project_id}/tasks", basic_task_data)
if result and "data" in result and len(result["data"]) > 0:
task_id = result["data"][0]["id"]
print(f" ✓ Created task '{task_title}': {task_id}")
# Now update with custom fields
if custom_fields:
print(f" Adding custom fields to task...")
update_data = {"customFields": custom_fields}
update_result = make_wrike_request("PUT", f"/tasks/{task_id}", update_data)
if update_result:
print(f" ✓ Custom fields added")
else:
print(f" ⚠ Warning: Failed to add custom fields")
return task_id
else:
print(f" ✗ Failed to create task '{task_title}'")
return None
def process_json_file(json_file_path):
"""Process a single JSON file and create Wrike structure."""
print(f"\n{'='*80}")
print(f"Processing: {json_file_path.name}")
print(f"{'='*80}")
try:
with open(json_file_path, 'r') as f:
data = json.load(f)
except Exception as e:
print(f"Error reading JSON file: {e}")
return False
# Extract data
job_spec = data.get("JobSpecification", {})
project_details = job_spec.get("ProjectDetails", {})
job_details = job_spec.get("JobDetails", {})
# 1. Get/Create top-level folder from BusinessArea
business_area = project_details.get("BusinessArea", job_details.get("BusinessArea", ""))
folder_name = parse_business_area(business_area)
if not folder_name:
print(" ✗ No BusinessArea found, skipping")
return False
print(f"\n1. Processing folder: '{folder_name}'")
folder_id = get_or_create_folder(folder_name)
if not folder_id:
print(f" ✗ Failed to get/create folder '{folder_name}'")
return False
# 2. Get/Create project
project_title = project_details.get("Title", "Untitled Project")
campaign_code = job_details.get("CampaignCode", "")
print(f"\n2. Processing project: '{project_title}'")
if campaign_code:
print(f" Campaign Code: {campaign_code}")
project_id = get_or_create_project(project_title, folder_id, project_details, campaign_code)
if not project_id:
print(f" ✗ Failed to get/create project '{project_title}'")
return False
# 3. Create or update deliverable task
print(f"\n3. Processing deliverable task")
task_id = create_or_update_deliverable_task(project_id, job_details)
if task_id:
# Check if task was skipped (already existed)
job_number = job_details.get("Number", "")
existing_task = find_task_by_omg_number(project_id, job_number) if job_number else None
if existing_task and existing_task == task_id:
# Task already existed, was skipped
print(f"\n⊙ Successfully processed {json_file_path.name} (task already exists)")
return "skipped"
else:
# New task was created
print(f"\n✓ Successfully processed {json_file_path.name}")
return True
else:
print(f"\n✗ Failed to create deliverable task for {json_file_path.name}")
return False
def move_to_processed(json_file, json_dir):
"""Move processed JSON file to Processed subfolder."""
processed_dir = json_dir / "Processed"
processed_dir.mkdir(exist_ok=True)
destination = processed_dir / json_file.name
try:
shutil.move(str(json_file), str(destination))
print(f" → Moved to: Processed/{json_file.name}")
return True
except Exception as e:
print(f" ✗ Failed to move file: {e}")
return False
def cleanup_old_files(json_dir, hours=24):
"""Delete files in Processed folder older than specified hours."""
processed_dir = json_dir / "Processed"
if not processed_dir.exists():
return 0
cutoff_time = time.time() - (hours * 3600)
deleted_count = 0
for file in processed_dir.glob("*.json"):
try:
file_age = file.stat().st_mtime
if file_age < cutoff_time:
file.unlink()
deleted_count += 1
print(f" Deleted old file: {file.name}")
except Exception as e:
print(f" Warning: Could not delete {file.name}: {e}")
return deleted_count
def main():
"""Main function to process all JSON files in a directory."""
if len(sys.argv) < 2:
print("Usage: python wrike_import.py <json_directory>")
print("\nExample: python wrike_import.py ./json_files/")
sys.exit(1)
json_dir = Path(sys.argv[1])
if not json_dir.exists() or not json_dir.is_dir():
print(f"Error: Directory '{json_dir}' does not exist")
sys.exit(1)
# Find all JSON files (exclude Processed subfolder)
json_files = [f for f in json_dir.glob("*.json") if "Processed" not in str(f)]
if not json_files:
print(f"No JSON files found in '{json_dir}'")
sys.exit(1)
print(f"\nFound {len(json_files)} JSON file(s) to process")
print(f"Target: Wrike Staging Space ({STAGING_SPACE_ID})")
# Process each file
success_count = 0
failed_count = 0
moved_count = 0
skipped_count = 0
for json_file in json_files:
try:
result = process_json_file(json_file)
if result == "skipped":
skipped_count += 1
success_count += 1 # Still count as success for file movement
# Move skipped file to processed
if move_to_processed(json_file, json_dir):
moved_count += 1
elif result:
success_count += 1
# Move successfully processed file
if move_to_processed(json_file, json_dir):
moved_count += 1
else:
failed_count += 1
except Exception as e:
print(f"\n✗ Unexpected error processing {json_file.name}: {e}")
failed_count += 1
# Cleanup old files in Processed folder
print(f"\n{'='*80}")
print(f"CLEANUP")
print(f"{'='*80}")
deleted_count = cleanup_old_files(json_dir, hours=24)
if deleted_count > 0:
print(f"Deleted {deleted_count} file(s) older than 24 hours from Processed folder")
else:
print("No old files to delete")
# Summary
print(f"\n{'='*80}")
print(f"SUMMARY")
print(f"{'='*80}")
print(f"Total files: {len(json_files)}")
print(f"Successful: {success_count}")
print(f"Skipped (already exists): {skipped_count}")
print(f"Failed: {failed_count}")
print(f"Moved to Processed: {moved_count}")
print(f"\nFolders created/found: {len(folder_cache)}")
print(f"Projects created/found: {len(project_cache)}")
if __name__ == "__main__":
main()

1064
wrike_monitor_lgl.py Normal file

File diff suppressed because it is too large Load diff