Fix extraction association parameter order and add comprehensive logging

- Correct parameter order in frontend JavaScript API calls
- Add detailed logging for all association operations (create, remove, bulk save)
- Log user context, operation details, and validation errors
- Fix parameter mapping: outcome_name (source) -> extraction_result (target)
- Update logging to correctly show association mappings
- Add logging for association validation and duplicate detection
parent 179aabf4
...@@ -2767,12 +2767,24 @@ def save_extraction_associations(): ...@@ -2767,12 +2767,24 @@ def save_extraction_associations():
if not associations_data: if not associations_data:
return jsonify({"error": "No associations provided"}), 400 return jsonify({"error": "No associations provided"}), 400
# Log bulk association save attempt
user_id = getattr(current_user, 'id', 'unknown') if current_user else 'unauthenticated'
user_role = getattr(current_user, 'role', 'unknown') if current_user else 'unauthenticated'
logger.info(f"EXTRACTION_ASSOCIATION_BULK_SAVE_ATTEMPT: user_id={user_id}, user_role={user_role}, "
f"association_count={len(associations_data)}, source=web_dashboard_api, "
f"method=bulk_save_associations")
session = api_bp.db_manager.get_session() session = api_bp.db_manager.get_session()
try: try:
# Get existing associations before clearing for logging
existing_associations = session.query(ExtractionAssociationModel).all()
existing_count = len(existing_associations)
# Clear existing associations # Clear existing associations
session.query(ExtractionAssociationModel).delete() session.query(ExtractionAssociationModel).delete()
# Add new associations - no limit on number per outcome # Add new associations - no limit on number per outcome
created_associations = []
for assoc_data in associations_data: for assoc_data in associations_data:
association = ExtractionAssociationModel( association = ExtractionAssociationModel(
outcome_name=assoc_data['outcome_name'], outcome_name=assoc_data['outcome_name'],
...@@ -2780,10 +2792,20 @@ def save_extraction_associations(): ...@@ -2780,10 +2792,20 @@ def save_extraction_associations():
is_default=False # User-created associations are not default is_default=False # User-created associations are not default
) )
session.add(association) session.add(association)
created_associations.append(association)
session.commit() session.commit()
logger.info(f"Saved {len(associations_data)} extraction associations") # Log details of the bulk save operation
logger.info(f"EXTRACTION_ASSOCIATION_BULK_SAVE_COMPLETED: user_id={user_id}, user_role={user_role}, "
f"cleared_count={existing_count}, saved_count={len(associations_data)}, "
f"source=web_dashboard_api, method=bulk_save_associations")
# Log individual associations created
for assoc in created_associations:
logger.info(f"EXTRACTION_ASSOCIATION_CREATED: user_id={user_id}, "
f"outcome_name={assoc.outcome_name}, extraction_result={assoc.extraction_result}, "
f"association_id={assoc.id}, source=web_dashboard_api, method=bulk_save_associations")
return jsonify({ return jsonify({
"success": True, "success": True,
...@@ -2813,23 +2835,33 @@ def add_extraction_association(): ...@@ -2813,23 +2835,33 @@ def add_extraction_association():
if not outcome_name or not extraction_result: if not outcome_name or not extraction_result:
return jsonify({"error": "outcome_name and extraction_result are required"}), 400 return jsonify({"error": "outcome_name and extraction_result are required"}), 400
# Log association creation attempt with source information
user_id = getattr(current_user, 'id', 'unknown') if current_user else 'unauthenticated'
user_role = getattr(current_user, 'role', 'unknown') if current_user else 'unauthenticated'
logger.info(f"EXTRACTION_ASSOCIATION_CREATION_ATTEMPT: user_id={user_id}, user_role={user_role}, "
f"outcome_name={outcome_name}, extraction_result={extraction_result}, "
f"source=web_dashboard_api, method=add_single_association")
# Get valid extraction results from available outcomes # Get valid extraction results from available outcomes
session = api_bp.db_manager.get_session() session = api_bp.db_manager.get_session()
try: try:
# Get distinct outcome names from match outcomes # Get distinct outcome names from match outcomes
outcomes_query = session.query(MatchOutcomeModel.column_name).distinct() outcomes_query = session.query(MatchOutcomeModel.column_name).distinct()
valid_results = [row[0] for row in outcomes_query.all()] valid_results = [row[0] for row in outcomes_query.all()]
# Add UNDER and OVER outcomes if not present # Add UNDER and OVER outcomes if not present
if 'UNDER' not in valid_results: if 'UNDER' not in valid_results:
valid_results.append('UNDER') valid_results.append('UNDER')
if 'OVER' not in valid_results: if 'OVER' not in valid_results:
valid_results.append('OVER') valid_results.append('OVER')
# Validate extraction_result values # Validate extraction_result values
if extraction_result not in valid_results: if extraction_result not in valid_results:
logger.warning(f"EXTRACTION_ASSOCIATION_INVALID_RESULT: user_id={user_id}, "
f"outcome_name={outcome_name}, extraction_result={extraction_result}, "
f"valid_results={valid_results}")
return jsonify({"error": f"extraction_result must be one of available outcomes: {', '.join(sorted(valid_results))}"}), 400 return jsonify({"error": f"extraction_result must be one of available outcomes: {', '.join(sorted(valid_results))}"}), 400
finally: finally:
session.close() session.close()
...@@ -2842,6 +2874,9 @@ def add_extraction_association(): ...@@ -2842,6 +2874,9 @@ def add_extraction_association():
).first() ).first()
if existing_assoc: if existing_assoc:
logger.warning(f"EXTRACTION_ASSOCIATION_DUPLICATE: user_id={user_id}, "
f"outcome_name={outcome_name}, extraction_result={extraction_result}, "
f"existing_id={existing_assoc.id}")
return jsonify({ return jsonify({
"success": False, "success": False,
"error": f"Association already exists: {outcome_name} -> {extraction_result}" "error": f"Association already exists: {outcome_name} -> {extraction_result}"
...@@ -2858,7 +2893,10 @@ def add_extraction_association(): ...@@ -2858,7 +2893,10 @@ def add_extraction_association():
session.add(association) session.add(association)
session.commit() session.commit()
logger.info(f"Added extraction association: {outcome_name} -> {extraction_result}") logger.info(f"EXTRACTION_ASSOCIATION_CREATED: user_id={user_id}, user_role={user_role}, "
f"outcome_name={outcome_name}, extraction_result={extraction_result}, "
f"association_id={association.id}, source=web_dashboard_api, "
f"method=add_single_association")
return jsonify({ return jsonify({
"success": True, "success": True,
...@@ -2888,6 +2926,13 @@ def remove_extraction_association(): ...@@ -2888,6 +2926,13 @@ def remove_extraction_association():
if not outcome_name or not extraction_result: if not outcome_name or not extraction_result:
return jsonify({"error": "outcome_name and extraction_result are required"}), 400 return jsonify({"error": "outcome_name and extraction_result are required"}), 400
# Log association removal attempt
user_id = getattr(current_user, 'id', 'unknown') if current_user else 'unauthenticated'
user_role = getattr(current_user, 'role', 'unknown') if current_user else 'unauthenticated'
logger.info(f"EXTRACTION_ASSOCIATION_REMOVAL_ATTEMPT: user_id={user_id}, user_role={user_role}, "
f"outcome_name={outcome_name}, extraction_result={extraction_result}, "
f"source=web_dashboard_api, method=remove_single_association")
session = api_bp.db_manager.get_session() session = api_bp.db_manager.get_session()
try: try:
# Find the specific association # Find the specific association
...@@ -2897,17 +2942,23 @@ def remove_extraction_association(): ...@@ -2897,17 +2942,23 @@ def remove_extraction_association():
).first() ).first()
if not association: if not association:
logger.warning(f"EXTRACTION_ASSOCIATION_NOT_FOUND: user_id={user_id}, "
f"outcome_name={outcome_name}, extraction_result={extraction_result}")
return jsonify({ return jsonify({
"success": False, "success": False,
"error": f"Association not found: {outcome_name} -> {extraction_result}" "error": f"Association not found: {outcome_name} -> {extraction_result}"
}), 404 }), 404
# Log the association being removed
logger.info(f"EXTRACTION_ASSOCIATION_REMOVED: user_id={user_id}, user_role={user_role}, "
f"outcome_name={outcome_name}, extraction_result={extraction_result}, "
f"association_id={association.id}, source=web_dashboard_api, "
f"method=remove_single_association")
# Remove the association # Remove the association
session.delete(association) session.delete(association)
session.commit() session.commit()
logger.info(f"Removed extraction association: {outcome_name} -> {extraction_result}")
return jsonify({ return jsonify({
"success": True, "success": True,
"message": f"Association removed: {outcome_name} -> {extraction_result}" "message": f"Association removed: {outcome_name} -> {extraction_result}"
......
...@@ -987,8 +987,8 @@ document.addEventListener('DOMContentLoaded', function() { ...@@ -987,8 +987,8 @@ document.addEventListener('DOMContentLoaded', function() {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
outcome_name: outcome, outcome_name: associatedOutcome,
extraction_result: associatedOutcome extraction_result: outcome
}) })
}) })
.then(response => response.json()) .then(response => response.json())
...@@ -1012,8 +1012,8 @@ document.addEventListener('DOMContentLoaded', function() { ...@@ -1012,8 +1012,8 @@ document.addEventListener('DOMContentLoaded', function() {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
outcome_name: outcome, outcome_name: associatedOutcome,
extraction_result: associatedOutcome extraction_result: outcome
}) })
}) })
.then(response => response.json()) .then(response => response.json())
...@@ -1134,8 +1134,8 @@ document.addEventListener('DOMContentLoaded', function() { ...@@ -1134,8 +1134,8 @@ document.addEventListener('DOMContentLoaded', function() {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
outcome_name: assoc.outcome_name, outcome_name: assoc.extraction_result,
extraction_result: assoc.extraction_result extraction_result: assoc.outcome_name
}) })
}) })
); );
......
# MbetterClient v1.2.11
Cross-platform multimedia client application
## Installation
1. Extract this package to your desired location
2. Run the executable file
3. The application will create necessary configuration files on first run
## System Requirements
- **Operating System**: Linux 6.16.3+deb14-amd64
- **Architecture**: x86_64
- **Memory**: 512 MB RAM minimum, 1 GB recommended
- **Disk Space**: 100 MB free space
## Configuration
The application stores its configuration and database in:
- **Windows**: `%APPDATA%\MbetterClient`
- **macOS**: `~/Library/Application Support/MbetterClient`
- **Linux**: `~/.config/MbetterClient`
## Web Interface
By default, the web interface is available at: http://localhost:5001
Default login credentials:
- Username: admin
- Password: admin
**Please change the default password after first login.**
## Support
For support and documentation, please visit: https://git.nexlab.net/mbetter/mbetterc
## Version Information
- Version: 1.2.11
- Build Date: zeiss
- Platform: Linux-6.16.3+deb14-amd64-x86_64-with-glibc2.41
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment