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

217 lines
8.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Student Skills Query Tool
This module provides a command-line interface for querying student skills
from the Open-ICT competency framework. The framework organizes skills by
skill name and proficiency levels, representing different competencies that
students should develop during their ICT education.
The data structure follows a two-dimensional matrix:
- Skill names (e.g., "Samenwerken", "Kritisch oordelen")
- Proficiency levels (1-4, representing increasing complexity)
Usage:
python vaardigheden.py [--skill SKILL] [--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 skill names as defined in the Open-ICT framework
# These represent core competencies that ICT students should develop
VALID_SKILLS = [
"Juiste kennis ontwikkelen", # Developing appropriate knowledge
"Kwalitatief product maken", # Creating quality products
"Overzicht creëren", # Creating overview
"Kritisch oordelen", # Critical judgment
"Samenwerken", # Collaboration
"Boodschap delen", # Sharing messages
"Plannen", # Planning
"Flexibel opstellen", # Being flexible
"Pro-actief handelen", # Proactive action
"Reflecteren" # Reflection
]
def load_data():
"""
Load student skills data from the JSON data file.
Reads the vaardigheden-nl.json file containing the complete student skills
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 student skills organized by:
{skill_name: {level: skill_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 / "vaardigheden-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_skills(data, skill_name=None, level=None):
"""
Filter student skills 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. Skill name (reduces dataset to single skill)
2. Level (filters specific proficiency levels within remaining skills)
Args:
data (dict): Complete competency framework data structure
skill_name (str, optional): Skill name to filter by (e.g., "Samenwerken")
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 skill name is not found in the data, or if
no skills match the specified filter combination
Note:
When no filters are specified, returns the complete dataset unchanged.
"""
result = {}
# First-level filter: narrow down to specific skill if requested
if skill_name:
if skill_name not in data:
print(f"Error: Skill '{skill_name}' not found", file=sys.stderr)
print(f"Valid skills: {', '.join(VALID_SKILLS)}", file=sys.stderr)
sys.exit(1)
# Reduce dataset to only the requested skill
data = {skill_name: data[skill_name]}
# Second-level filter: apply level filter if specified
if level:
# Iterate through all skills in the dataset (may be filtered or complete)
for skill, levels in data.items():
# Use dictionary comprehension to filter levels efficiently
filtered_levels = {lvl: content for lvl, content in levels.items() if lvl == level}
# Only include skill if it has matching levels
if filtered_levels:
result[skill] = filtered_levels
else:
# No level filter: include all skills and levels
result = data
# Validate that at least one skill matches the filter criteria
if not result:
print("No skills 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 student skills from the Open-ICT competency framework.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=f"""
Valid skills:
{chr(10).join(' - ' + skill for skill in VALID_SKILLS)}
Examples:
%(prog)s
%(prog)s --skill "Samenwerken"
%(prog)s --skill "Samenwerken" --level 2
%(prog)s --level 3
"""
)
# Define command-line arguments
# All arguments are optional, allowing flexible querying
parser.add_argument(
'--skill',
help='Filter by skill name'
)
# 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: validate skill name against our constants
# This provides better error messages than relying on filter_skills()
if args.skill and args.skill not in VALID_SKILLS:
print(f"Error: Invalid skill: {args.skill}", file=sys.stderr)
print(f"Valid skills: {', '.join(VALID_SKILLS)}", file=sys.stderr)
sys.exit(1)
# Execute the main workflow: load data, apply filters, output results
data = load_data()
result = filter_skills(data, args.skill, 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()