forked from Zhubei_iXD/report_skill_expm
feat: add list_user skill
This commit is contained in:
parent
0db7c83733
commit
501b9f5ddd
|
|
@ -0,0 +1,331 @@
|
|||
---
|
||||
name: list_user
|
||||
description: "User management and name mapping utility. Provides nickname-to-formal-name conversions, user validation, and metadata retrieval. Designed to support multiple report generation skills with consistent user information handling."
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# User Manager Skill
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides centralized user management and name mapping functionality. It's designed to support report generation and other skills that need to transform user nicknames (informal names) into formal Chinese names or other user metadata.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- You need to map user nicknames to formal names in reports
|
||||
- You want to validate user existence and metadata
|
||||
- You need to filter users by team or department
|
||||
- You're building report generators that require consistent user information
|
||||
- You need case-insensitive user name lookup
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Name Mapping**: Convert nicknames to formal names with configurable fallback behavior
|
||||
- **User Metadata**: Store and retrieve user information (team, department, etc.)
|
||||
- **Flexible Lookup**: Case-insensitive name matching with configurable behavior
|
||||
- **Batch Operations**: Map multiple names at once
|
||||
- **User Validation**: Check if users exist and identify missing entries
|
||||
- **Filtering**: Query users by team or department
|
||||
- **Reloadable**: Update mappings without restarting the application
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
.claude/skills/list_user/
|
||||
├── user_manager.py # Main module with UserManager class
|
||||
├── SKILL.md # This file
|
||||
├── references/
|
||||
│ └── user_mapping.json # User data and name mappings
|
||||
└── README.md # Usage documentation
|
||||
```
|
||||
|
||||
## User Mapping Format
|
||||
|
||||
The `user_mapping.json` file contains user data in the following format:
|
||||
|
||||
```json
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"nickname": "ben sung", # Informal/English name
|
||||
"formal_name": "宋柏昆", # Formal Chinese name
|
||||
"english_name": "Ben Sung", # Full English name
|
||||
"team": "智能控制組", # Team assignment
|
||||
"department": "軟體部" # Department
|
||||
}
|
||||
],
|
||||
"mapping_config": {
|
||||
"default_field": "formal_name", # Field returned by get_formal_name()
|
||||
"case_sensitive": false, # Whether lookup is case-sensitive
|
||||
"fallback_behavior": "keep_original" # 'keep_original', 'strict', or 'none'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```python
|
||||
from user_manager import UserManager
|
||||
|
||||
# Initialize (uses default path if not specified)
|
||||
manager = UserManager()
|
||||
|
||||
# Map a single nickname to formal name
|
||||
formal_name = manager.get_formal_name("ben sung")
|
||||
# Returns: "宋柏昆"
|
||||
|
||||
# Map multiple names
|
||||
names = ["ben sung", "Ryan Hsueh"]
|
||||
formal_names = manager.map_names(names)
|
||||
# Returns: ["宋柏昆", "徐瑞恩"]
|
||||
```
|
||||
|
||||
### Get User Information
|
||||
|
||||
```python
|
||||
# Get complete user record
|
||||
user = manager.get_user_info("ben sung")
|
||||
# Returns: {
|
||||
# "nickname": "ben sung",
|
||||
# "formal_name": "宋柏昆",
|
||||
# "english_name": "Ben Sung",
|
||||
# "team": "智能控制組",
|
||||
# "department": "軟體部"
|
||||
# }
|
||||
|
||||
# Get all users
|
||||
all_users = manager.get_all_users()
|
||||
|
||||
# Filter by team
|
||||
team_users = manager.get_users_by_team("智能控制組")
|
||||
|
||||
# Filter by department
|
||||
dept_users = manager.get_users_by_department("軟體部")
|
||||
```
|
||||
|
||||
### Validate Users
|
||||
|
||||
```python
|
||||
# Check if nicknames exist in mapping
|
||||
found, missing = manager.validate_users(
|
||||
["ben sung", "unknown person", "Ryan Hsueh"]
|
||||
)
|
||||
# found: ["ben sung", "Ryan Hsueh"]
|
||||
# missing: ["unknown person"]
|
||||
```
|
||||
|
||||
### Fallback Behaviors
|
||||
|
||||
The `get_formal_name()` method supports three fallback behaviors when a nickname is not found:
|
||||
|
||||
```python
|
||||
# 1. Keep original (default)
|
||||
name = manager.get_formal_name("unknown", fallback='keep_original')
|
||||
# Returns: "unknown"
|
||||
|
||||
# 2. Strict mode (raise error)
|
||||
try:
|
||||
name = manager.get_formal_name("unknown", fallback='strict')
|
||||
except ValueError as e:
|
||||
print(f"User not found: {e}")
|
||||
|
||||
# 3. Return None
|
||||
name = manager.get_formal_name("unknown", fallback='none')
|
||||
# Returns: None
|
||||
```
|
||||
|
||||
### Using the Singleton Instance
|
||||
|
||||
```python
|
||||
from user_manager import get_default_manager
|
||||
|
||||
# Get the singleton instance
|
||||
manager = get_default_manager()
|
||||
|
||||
# Use the same manager instance throughout your application
|
||||
formal_name = manager.get_formal_name("ben sung")
|
||||
```
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
### Example: Integrating with week_report_gen
|
||||
|
||||
```python
|
||||
from user_manager import get_default_manager
|
||||
|
||||
# In your report generation code
|
||||
user_manager = get_default_manager()
|
||||
|
||||
# Transform participant list from nicknames to formal names
|
||||
participants_nicknames = ["ben sung", "Ryan Hsueh"]
|
||||
participants_formal = user_manager.map_names(participants_nicknames)
|
||||
|
||||
# Use in report
|
||||
print(f"Team: {', '.join(participants_formal)}")
|
||||
# Output: Team: 宋柏昆, 徐瑞恩
|
||||
```
|
||||
|
||||
## Adding New Users
|
||||
|
||||
To add new users to the system:
|
||||
|
||||
1. Edit `references/user_mapping.json`
|
||||
2. Add a new entry to the `users` array:
|
||||
|
||||
```json
|
||||
{
|
||||
"nickname": "new user",
|
||||
"formal_name": "新用户",
|
||||
"english_name": "New User",
|
||||
"team": "智能控制組",
|
||||
"department": "軟體部"
|
||||
}
|
||||
```
|
||||
|
||||
3. Reload the manager to pick up changes:
|
||||
|
||||
```python
|
||||
manager.reload()
|
||||
```
|
||||
|
||||
Or create a new instance:
|
||||
|
||||
```python
|
||||
from user_manager import UserManager
|
||||
manager = UserManager() # Loads the latest mapping
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
The `mapping_config` section in `user_mapping.json` controls lookup behavior:
|
||||
|
||||
| Option | Type | Values | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| `default_field` | string | Any user field | Which field to return in `get_formal_name()` |
|
||||
| `case_sensitive` | boolean | true/false | Whether nickname matching is case-sensitive |
|
||||
| `fallback_behavior` | string | 'keep_original', 'strict', 'none' | Default behavior when user not found |
|
||||
|
||||
### Example Configurations
|
||||
|
||||
**Chinese-focused** (current default):
|
||||
```json
|
||||
"mapping_config": {
|
||||
"default_field": "formal_name",
|
||||
"case_sensitive": false,
|
||||
"fallback_behavior": "keep_original"
|
||||
}
|
||||
```
|
||||
|
||||
**English-focused**:
|
||||
```json
|
||||
"mapping_config": {
|
||||
"default_field": "english_name",
|
||||
"case_sensitive": false,
|
||||
"fallback_behavior": "keep_original"
|
||||
}
|
||||
```
|
||||
|
||||
**Strict validation**:
|
||||
```json
|
||||
"mapping_config": {
|
||||
"default_field": "formal_name",
|
||||
"case_sensitive": true,
|
||||
"fallback_behavior": "strict"
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### UserManager Class
|
||||
|
||||
#### Constructor
|
||||
|
||||
```python
|
||||
UserManager(mapping_file: Optional[str] = None)
|
||||
```
|
||||
Initialize UserManager. If `mapping_file` is None, uses default location.
|
||||
|
||||
#### Methods
|
||||
|
||||
**`get_formal_name(nickname: str, fallback: str = 'keep_original') -> str`**
|
||||
- Get formal name for a nickname
|
||||
- Fallback options: 'keep_original', 'strict', 'none'
|
||||
|
||||
**`get_user_info(nickname: str) -> Optional[Dict]`**
|
||||
- Get complete user record
|
||||
|
||||
**`map_names(nicknames: List[str], fallback: str = 'keep_original') -> List[str]`**
|
||||
- Map multiple nicknames to formal names
|
||||
|
||||
**`get_all_users() -> List[Dict]`**
|
||||
- Return all user records
|
||||
|
||||
**`get_users_by_team(team: str) -> List[Dict]`**
|
||||
- Filter users by team
|
||||
|
||||
**`get_users_by_department(department: str) -> List[Dict]`**
|
||||
- Filter users by department
|
||||
|
||||
**`validate_users(nicknames: List[str]) -> Tuple[List[str], List[str]]`**
|
||||
- Validate nicknames, return (found, missing)
|
||||
|
||||
**`reload()`**
|
||||
- Reload mappings from file
|
||||
|
||||
### Module Functions
|
||||
|
||||
**`get_default_manager(mapping_file: Optional[str] = None) -> UserManager`**
|
||||
- Get or create singleton instance
|
||||
|
||||
## Error Handling
|
||||
|
||||
The skill handles several error scenarios:
|
||||
|
||||
| Error | When | Handling |
|
||||
|-------|------|----------|
|
||||
| FileNotFoundError | Mapping file not found | Raised immediately |
|
||||
| JSONDecodeError | Invalid JSON syntax | Raised with message |
|
||||
| User not found | Nickname lookup fails | Depends on fallback setting |
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
try:
|
||||
manager = UserManager()
|
||||
except FileNotFoundError:
|
||||
print("User mapping file is missing!")
|
||||
except ValueError:
|
||||
print("User mapping file has invalid JSON!")
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the module directly to test basic functionality:
|
||||
|
||||
```bash
|
||||
cd .claude/skills/list_user
|
||||
python user_manager.py
|
||||
```
|
||||
|
||||
This will display:
|
||||
- Name mapping examples
|
||||
- User information retrieval
|
||||
- Team filtering
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
|
||||
- Database backend (SQLite, PostgreSQL) instead of JSON
|
||||
- LDAP/Active Directory integration for enterprise environments
|
||||
- Role-based access control (RBAC) per user
|
||||
- User status tracking (active, inactive, etc.)
|
||||
- Export/import utilities for user data
|
||||
- Audit logging for user lookup operations
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"users": [
|
||||
{
|
||||
"nickname": "ben sung",
|
||||
"formal_name": "宋柏昆",
|
||||
"english_name": "Ben Sung",
|
||||
"team": "智能控制組",
|
||||
"department": "軟體部"
|
||||
},
|
||||
{
|
||||
"nickname": "Ryan Hsueh",
|
||||
"formal_name": "薛裕弘",
|
||||
"english_name": "Ryan Hsueh",
|
||||
"team": "智能控制組",
|
||||
"department": "軟體部"
|
||||
},
|
||||
{
|
||||
"nickname": "傑 羅",
|
||||
"formal_name": "羅傑",
|
||||
"english_name": "Jie Luo",
|
||||
"team": "智能控制組",
|
||||
"department": "軟體部"
|
||||
},
|
||||
{
|
||||
"nickname": "kenta kinoshita",
|
||||
"formal_name": "楊博軒",
|
||||
"english_name": "Kenta Kinoshita",
|
||||
"team": "智能控制組",
|
||||
"department": "軟體部"
|
||||
},
|
||||
{
|
||||
"nickname": "暉庭 洪",
|
||||
"formal_name": "洪暉庭",
|
||||
"english_name": "Hong Hui-Ting",
|
||||
"team": "智能控制組",
|
||||
"department": "軟體部"
|
||||
},
|
||||
{
|
||||
"nickname": "庭宇 施",
|
||||
"formal_name": "施庭宇",
|
||||
"english_name": "Shi Ting-Yu",
|
||||
"team": "智能控制組",
|
||||
"department": "軟體部"
|
||||
}
|
||||
],
|
||||
"mapping_config": {
|
||||
"default_field": "formal_name",
|
||||
"case_sensitive": false,
|
||||
"fallback_behavior": "keep_original"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
"""
|
||||
User Manager - Handles user name mapping and lookup
|
||||
|
||||
This module provides utilities for managing user information, including
|
||||
nickname-to-formal-name mappings, user validation, and metadata retrieval.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
|
||||
class UserManager:
|
||||
"""
|
||||
Manages user information and name mappings.
|
||||
|
||||
Supports:
|
||||
- Looking up formal names from nicknames
|
||||
- Validating user existence
|
||||
- Retrieving user metadata (team, department, etc.)
|
||||
- Case-insensitive name matching
|
||||
"""
|
||||
|
||||
def __init__(self, mapping_file: Optional[str] = None):
|
||||
"""
|
||||
Initialize UserManager with user mapping data.
|
||||
|
||||
Args:
|
||||
mapping_file: Path to user_mapping.json. If None, uses default location.
|
||||
"""
|
||||
if mapping_file is None:
|
||||
# Use default location relative to this script
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
mapping_file = os.path.join(script_dir, 'references', 'user_mapping.json')
|
||||
|
||||
self.mapping_file = mapping_file
|
||||
self.users: List[Dict] = []
|
||||
self.nickname_index: Dict[str, Dict] = {}
|
||||
self.config: Dict = {}
|
||||
|
||||
self._load_mapping()
|
||||
|
||||
def _load_mapping(self):
|
||||
"""Load user mapping from JSON file."""
|
||||
if not os.path.exists(self.mapping_file):
|
||||
raise FileNotFoundError(f"User mapping file not found: {self.mapping_file}")
|
||||
|
||||
try:
|
||||
with open(self.mapping_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
self.users = data.get('users', [])
|
||||
self.config = data.get('mapping_config', {})
|
||||
|
||||
# Build nickname index for fast lookup
|
||||
self._build_index()
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON in user mapping file: {e}")
|
||||
|
||||
def _build_index(self):
|
||||
"""Build case-insensitive index of nicknames."""
|
||||
case_sensitive = self.config.get('case_sensitive', False)
|
||||
|
||||
for user in self.users:
|
||||
nickname = user.get('nickname', '')
|
||||
key = nickname if case_sensitive else nickname.lower()
|
||||
self.nickname_index[key] = user
|
||||
|
||||
def get_formal_name(self, nickname: str, fallback: str = 'keep_original') -> str:
|
||||
"""
|
||||
Get formal name for a given nickname.
|
||||
|
||||
Args:
|
||||
nickname: The nickname to look up
|
||||
fallback: Behavior if nickname not found:
|
||||
- 'keep_original': Return original nickname
|
||||
- 'strict': Raise ValueError
|
||||
- 'none': Return None
|
||||
|
||||
Returns:
|
||||
Formal name, or fallback value if not found
|
||||
|
||||
Raises:
|
||||
ValueError: If fallback='strict' and nickname not found
|
||||
"""
|
||||
case_sensitive = self.config.get('case_sensitive', False)
|
||||
key = nickname if case_sensitive else nickname.lower()
|
||||
|
||||
if key in self.nickname_index:
|
||||
user = self.nickname_index[key]
|
||||
field = self.config.get('default_field', 'formal_name')
|
||||
return user.get(field, nickname)
|
||||
|
||||
# Handle fallback
|
||||
if fallback == 'strict':
|
||||
raise ValueError(f"User nickname not found: {nickname}")
|
||||
elif fallback == 'none':
|
||||
return None
|
||||
else: # 'keep_original'
|
||||
return nickname
|
||||
|
||||
def get_user_info(self, nickname: str) -> Optional[Dict]:
|
||||
"""
|
||||
Get complete user information.
|
||||
|
||||
Args:
|
||||
nickname: The nickname to look up
|
||||
|
||||
Returns:
|
||||
Dict with user info, or None if not found
|
||||
"""
|
||||
case_sensitive = self.config.get('case_sensitive', False)
|
||||
key = nickname if case_sensitive else nickname.lower()
|
||||
|
||||
return self.nickname_index.get(key)
|
||||
|
||||
def map_names(self, nicknames: List[str], fallback: str = 'keep_original') -> List[str]:
|
||||
"""
|
||||
Map multiple nicknames to formal names.
|
||||
|
||||
Args:
|
||||
nicknames: List of nicknames
|
||||
fallback: Behavior if nickname not found (see get_formal_name)
|
||||
|
||||
Returns:
|
||||
List of formal names
|
||||
"""
|
||||
return [self.get_formal_name(nick, fallback) for nick in nicknames]
|
||||
|
||||
def get_all_users(self) -> List[Dict]:
|
||||
"""Get all user records."""
|
||||
return self.users.copy()
|
||||
|
||||
def get_users_by_team(self, team: str) -> List[Dict]:
|
||||
"""Get all users in a specific team."""
|
||||
return [u for u in self.users if u.get('team') == team]
|
||||
|
||||
def get_users_by_department(self, department: str) -> List[Dict]:
|
||||
"""Get all users in a specific department."""
|
||||
return [u for u in self.users if u.get('department') == department]
|
||||
|
||||
def validate_users(self, nicknames: List[str]) -> Tuple[List[str], List[str]]:
|
||||
"""
|
||||
Validate that nicknames exist in the mapping.
|
||||
|
||||
Args:
|
||||
nicknames: List of nicknames to validate
|
||||
|
||||
Returns:
|
||||
Tuple of (found_nicknames, missing_nicknames)
|
||||
"""
|
||||
case_sensitive = self.config.get('case_sensitive', False)
|
||||
found = []
|
||||
missing = []
|
||||
|
||||
for nick in nicknames:
|
||||
key = nick if case_sensitive else nick.lower()
|
||||
if key in self.nickname_index:
|
||||
found.append(nick)
|
||||
else:
|
||||
missing.append(nick)
|
||||
|
||||
return found, missing
|
||||
|
||||
def reload(self):
|
||||
"""Reload user mapping from file."""
|
||||
self._load_mapping()
|
||||
|
||||
|
||||
# Singleton instance (optional - for convenience)
|
||||
_default_manager: Optional[UserManager] = None
|
||||
|
||||
|
||||
def get_default_manager(mapping_file: Optional[str] = None) -> UserManager:
|
||||
"""
|
||||
Get or create the default UserManager instance.
|
||||
|
||||
Args:
|
||||
mapping_file: Path to user_mapping.json (only used on first call)
|
||||
|
||||
Returns:
|
||||
UserManager instance
|
||||
"""
|
||||
global _default_manager
|
||||
if _default_manager is None:
|
||||
_default_manager = UserManager(mapping_file)
|
||||
return _default_manager
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
manager = UserManager()
|
||||
|
||||
print("=" * 60)
|
||||
print("USER MANAGER - EXAMPLE")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Test name lookup
|
||||
test_names = ["ben sung", "Ryan Hsueh", "傑 羅"]
|
||||
print("Name Mapping Test:")
|
||||
print("-" * 60)
|
||||
for nickname in test_names:
|
||||
formal = manager.get_formal_name(nickname)
|
||||
print(f" {nickname:20} → {formal}")
|
||||
print()
|
||||
|
||||
# Test user info
|
||||
print("User Information:")
|
||||
print("-" * 60)
|
||||
user_info = manager.get_user_info("ben sung")
|
||||
if user_info:
|
||||
for key, value in user_info.items():
|
||||
print(f" {key:20} : {value}")
|
||||
print()
|
||||
|
||||
# Test team lookup
|
||||
print("Users by Team (智能控制組):")
|
||||
print("-" * 60)
|
||||
team_users = manager.get_users_by_team("智能控制組")
|
||||
for user in team_users:
|
||||
print(f" {user.get('nickname'):20} ({user.get('formal_name')})")
|
||||
print()
|
||||
|
||||
print("=" * 60)
|
||||
|
|
@ -10,6 +10,22 @@ from datetime import datetime
|
|||
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
||||
from copy import copy
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add list_user skill to path for user mapping
|
||||
# Current structure: .../week_report_gen/generate_report.py
|
||||
# Target: .../list_user/user_manager.py
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
skills_dir = os.path.dirname(script_dir) # .../skills
|
||||
list_user_path = os.path.join(skills_dir, 'list_user')
|
||||
if list_user_path not in sys.path:
|
||||
sys.path.insert(0, list_user_path)
|
||||
|
||||
try:
|
||||
from user_manager import UserManager
|
||||
USER_MANAGER_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
USER_MANAGER_AVAILABLE = False
|
||||
|
||||
|
||||
# Style constants for consistent coloring
|
||||
|
|
@ -156,10 +172,14 @@ def aggregate_work_hours(df):
|
|||
return summary, start_date, end_date
|
||||
|
||||
|
||||
def format_for_weekly_report(summary_df):
|
||||
def format_for_weekly_report(summary_df, user_manager=None):
|
||||
"""
|
||||
Transform aggregated data into weekly report format.
|
||||
Group entries by project and list participants.
|
||||
|
||||
Args:
|
||||
summary_df: Aggregated work data
|
||||
user_manager: Optional UserManager instance for name mapping
|
||||
"""
|
||||
report_data = []
|
||||
|
||||
|
|
@ -175,7 +195,14 @@ def format_for_weekly_report(summary_df):
|
|||
for _, row in project_data.iterrows():
|
||||
user_name = row['使用者']
|
||||
user_hours = row['工時']
|
||||
participants.append(f"{user_name}")
|
||||
|
||||
# Map nickname to formal name if user manager is available
|
||||
if user_manager:
|
||||
formal_name = user_manager.get_formal_name(user_name, fallback='keep_original')
|
||||
participants.append(formal_name)
|
||||
else:
|
||||
participants.append(f"{user_name}")
|
||||
|
||||
total_hours += user_hours
|
||||
|
||||
if '活動' in row and row['活動'] and row['活動'] != '-' and pd.notna(row['活動']):
|
||||
|
|
@ -205,11 +232,12 @@ def format_for_weekly_report(summary_df):
|
|||
'代碼上傳': None,
|
||||
'下周計畫': None
|
||||
})
|
||||
|
||||
|
||||
return pd.DataFrame(report_data)
|
||||
|
||||
|
||||
def generate_weekly_report(input_file, output_file, template_file=None, team_name="智能控制組"):
|
||||
def generate_weekly_report(input_file, output_file, template_file=None, team_name="智能控制組", use_user_mapping=True):
|
||||
"""
|
||||
Main function to generate weekly report from cost report.
|
||||
|
||||
|
|
@ -218,11 +246,25 @@ def generate_weekly_report(input_file, output_file, template_file=None, team_nam
|
|||
output_file: Path to output weekly report Excel file
|
||||
template_file: Optional path to template file (if not using default)
|
||||
team_name: Team name for sheet title (default: "智能控制組")
|
||||
use_user_mapping: Whether to apply user name mapping (default: True)
|
||||
|
||||
Returns:
|
||||
Tuple of (output_file_path, report_dataframe)
|
||||
"""
|
||||
# Initialize user manager for name mapping
|
||||
user_manager = None
|
||||
if use_user_mapping and USER_MANAGER_AVAILABLE:
|
||||
try:
|
||||
user_manager = UserManager()
|
||||
print("✓ User name mapping enabled")
|
||||
except Exception as e:
|
||||
print(f"⚠ Warning: Could not load user mapping: {e}")
|
||||
print(" Continuing without user name mapping...")
|
||||
elif use_user_mapping and not USER_MANAGER_AVAILABLE:
|
||||
print("⚠ Warning: list_user skill not available, skipping user name mapping")
|
||||
|
||||
print(f"Reading cost report from: {input_file}")
|
||||
|
||||
|
||||
# Read and process input
|
||||
df = read_cost_report(input_file)
|
||||
|
|
@ -232,7 +274,7 @@ def generate_weekly_report(input_file, output_file, template_file=None, team_nam
|
|||
print(f"Date range: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}")
|
||||
print(f"Found {len(summary)} work entries across {summary['專案'].nunique()} projects")
|
||||
|
||||
report_df = format_for_weekly_report(summary)
|
||||
report_df = format_for_weekly_report(summary, user_manager=user_manager)
|
||||
print(f"Formatted {len(report_df)} project entries for report")
|
||||
|
||||
# Load template or create new workbook
|
||||
|
|
|
|||
Loading…
Reference in New Issue