Files
gh-gijsbartman-hogeschool-u…/skills/competenties/scripts/hboi.py
2025-11-29 18:28:25 +08:00

253 lines
9.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""
HBO-I Professional Tasks Query Tool
This module provides a command-line interface for querying professional tasks
from the Open-ICT competency framework for HBO-I (Higher Professional Education
in Information Technology). The framework organizes tasks by architecture layers,
activities, and proficiency levels.
The data structure follows a three-dimensional matrix:
- Architecture layers (e.g., Software, Infrastructure)
- Activities (e.g., Analyseren, Ontwerpen, Realiseren)
- Proficiency levels (1-4, representing increasing complexity)
Usage:
python hboi.py [--layer LAYER] [--activity ACTIVITY] [--level LEVEL]
Exit Codes:
0: Success
1: Error (invalid input, missing data file, or no results found)
"""
import json
import sys
import argparse
from pathlib import Path
# Directory paths for locating data files relative to script location
SCRIPT_DIR = Path(__file__).parent
DATA_DIR = SCRIPT_DIR.parent / "data"
# Valid architecture layers as defined in the Open-ICT framework
# These represent different technical domains in ICT systems
VALID_LAYERS = [
"Gebruikersinteractie", # User interaction layer
"Organisatieprocessen", # Organizational processes layer
"Infrastructuur", # Infrastructure layer
"Software", # Software layer
"Hardwareinterfacing" # Hardware interfacing layer
]
# Valid professional activities as defined in the Open-ICT framework
# These represent different phases of the development lifecycle
VALID_ACTIVITIES = [
"Analyseren", # Analysis phase
"Adviseren", # Advisory phase
"Ontwerpen", # Design phase
"Realiseren", # Implementation phase
"Manage & Control" # Management and control phase
]
def load_data():
"""
Load professional tasks data from the JSON data file.
Reads the hboi-nl.json file containing the complete HBO-I competency
framework data. The file is expected to be in the data directory relative
to this script's location.
Returns:
dict: Nested dictionary structure containing professional tasks organized by:
{layer: {activity: {level: task_description}}}
Raises:
SystemExit: If the data file is not found, contains invalid JSON,
or cannot be read for any other reason.
Note:
All errors are written to stderr before exiting with status code 1.
"""
data_file = DATA_DIR / "hboi-nl.json"
# Verify data file exists before attempting to read
if not data_file.exists():
print(f"Error: Data file not found: {data_file}", file=sys.stderr)
sys.exit(1)
try:
# Open with explicit UTF-8 encoding to handle Dutch characters
with open(data_file, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError as e:
# Provide specific error for malformed JSON
print(f"Error: Invalid JSON in data file: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
# Catch-all for other file reading errors (permissions, I/O, etc.)
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
def filter_tasks(data, layer=None, activity=None, level=None):
"""
Filter professional tasks based on specified criteria.
Applies one or more filters to the competency framework data to narrow down
the results. Filters can be combined and are applied in the following order:
1. Architecture layer (reduces dataset to single layer)
2. Activity (filters activities within remaining layers)
3. Level (filters specific proficiency levels within remaining activities)
Args:
data (dict): Complete competency framework data structure
layer (str, optional): Architecture layer to filter by (e.g., "Software")
activity (str, optional): Activity type to filter by (e.g., "Ontwerpen")
level (str, optional): Proficiency level to filter by ("1", "2", "3", or "4")
Returns:
dict: Filtered data structure maintaining the same nested format as input,
but containing only entries matching all specified filters
Raises:
SystemExit: If the specified layer is not found in the data, or if no
tasks match the specified filter combination
Note:
When no filters are specified, returns the complete dataset unchanged.
"""
result = {}
# First-level filter: narrow down to specific architecture layer if requested
if layer:
if layer not in data:
print(f"Error: Architecture layer '{layer}' not found", file=sys.stderr)
print(f"Valid layers: {', '.join(VALID_LAYERS)}", file=sys.stderr)
sys.exit(1)
# Reduce dataset to only the requested layer
data = {layer: data[layer]}
# Second and third level filters: iterate through remaining layers
# and apply activity and/or level filters
for layer_name, activities in data.items():
filtered_activities = {}
for activity_name, levels in activities.items():
# Skip activities that don't match the activity filter
if activity and activity_name != activity:
continue
# Apply level filter if specified
if level:
# Use dictionary comprehension to filter levels efficiently
filtered_levels = {lvl: content for lvl, content in levels.items() if lvl == level}
# Only include activity if it has matching levels
if filtered_levels:
filtered_activities[activity_name] = filtered_levels
else:
# No level filter: include all levels for this activity
filtered_activities[activity_name] = levels
# Only include layer if it has matching activities after filtering
if filtered_activities:
result[layer_name] = filtered_activities
# Validate that at least one task matches the filter criteria
if not result:
print("No professional tasks found with the specified filters", file=sys.stderr)
sys.exit(1)
return result
def main():
"""
Main entry point for the command-line interface.
Parses command-line arguments, validates user input, loads the competency
framework data, applies filters, and outputs the results as formatted JSON.
The function handles all user interaction and orchestrates the data loading
and filtering pipeline. All errors are reported to stderr with helpful
messages, and the program exits with appropriate status codes.
Returns:
None: Outputs results to stdout and exits with status code 0 on success,
or exits with status code 1 on error
Side Effects:
- Reads from data file system
- Writes to stdout (JSON results)
- Writes to stderr (error messages)
- Calls sys.exit() on errors or successful completion
"""
# Configure argument parser with detailed help text
# RawDescriptionHelpFormatter preserves formatting in epilog
parser = argparse.ArgumentParser(
description="Retrieve HBO-I professional tasks from the Open-ICT competency framework.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=f"""
Architecture layers:
{chr(10).join(' - ' + layer for layer in VALID_LAYERS)}
Activities:
{chr(10).join(' - ' + activity for activity in VALID_ACTIVITIES)}
Examples:
%(prog)s
%(prog)s --layer "Software"
%(prog)s --activity "Ontwerpen"
%(prog)s --layer "Software" --activity "Realiseren"
%(prog)s --layer "Software" --level 2
%(prog)s --level 3
"""
)
# Define command-line arguments
# All arguments are optional, allowing flexible querying
parser.add_argument(
'--layer',
help='Filter by architecture layer'
)
parser.add_argument(
'--activity',
help='Filter by activity'
)
# Level argument uses choices to restrict valid values at parse time
parser.add_argument(
'--level',
type=str,
choices=['1', '2', '3', '4'],
help='Filter by level (1-4)'
)
# Parse command-line arguments (exits with error message if invalid)
args = parser.parse_args()
# Additional validation: argparse handles level choices, but we validate
# layer and activity against our constants to ensure consistency
# This provides better error messages than relying on filter_tasks()
if args.layer and args.layer not in VALID_LAYERS:
print(f"Error: Invalid architecture layer: {args.layer}", file=sys.stderr)
print(f"Valid layers: {', '.join(VALID_LAYERS)}", file=sys.stderr)
sys.exit(1)
if args.activity and args.activity not in VALID_ACTIVITIES:
print(f"Error: Invalid activity: {args.activity}", file=sys.stderr)
print(f"Valid activities: {', '.join(VALID_ACTIVITIES)}", file=sys.stderr)
sys.exit(1)
# Execute the main workflow: load data, apply filters, output results
data = load_data()
result = filter_tasks(data, args.layer, args.activity, args.level)
# Output filtered results as formatted JSON
# ensure_ascii=False preserves Dutch characters (e.g., é, ë)
# indent=2 provides human-readable formatting for debugging
print(json.dumps(result, ensure_ascii=False, indent=2))
# Entry point: only execute main() when script is run directly (not when imported)
if __name__ == "__main__":
main()