SuffolkLITLab / docassemble-AssemblyLine

Quickly go from a paper court form to a runnable, guided, step-by-step web application powered by Docassemble. Swap out branding and pre-built questions to meet your needs.
https://suffolklitlab.org/docassemble-AssemblyLine-documentation/
MIT License
41 stars 5 forks source link

'Answer Sets' Enhancement Request: Give user option to overwrite existing answer set or save as new #876

Open aidanielson opened 1 month ago

aidanielson commented 1 month ago

The existing sessions.py, "Answer Sets," feature allows DA users to save a new Answer Set. Good so far. But then when the user goes back to that same Answer Set and makes changes (or not) and wants to update the Answer Set to capture any changed or added data, the existing code saves the existing set as a separate Answer Set with the same title (same metadata: 'title'), resulting in multiple copies of the identical set name saved into the saved_interview_list. This feature enhancement request proposes to do the following:

  1. If the Answer Set already exists (i.e., the same metadata: 'title' already exists in the saved interview list when the user goes to save the Answer Set), provide a radio button question (e.g., "Answer Set {metadata: 'title'} already exists. Would you like to overwrite the existing Answer Set, or else save your current answers as a new Answer Set with a different title?") allowing the user to choose what they'd like to do.

  2. Specifically, the two options would be:

a. Completely overwrite/save over the existing Answer Set (or delete the existing set and then save the current one) with the current answer data;

b. Save the current Answer Set as a new set, and provide the user an area input question to enter the title of the current set, with the default value being the existing Answer Set title appended by a datetime stamp (e.g., "Jones v. Smith [2024-08-12]").

As a final matter, this request proposes to refactor the existing sessions.py and Answer Sets documentation to use separate terminology to more clearly define the "metadata: 'title'" of the Answer Set, and "answer_set_title" is proposed, as it avoids the 'filename' confusion with the "docassemble.AssemblyLine:al_saved_sessions_store.yml" file, which is already aggravated by other arguments and similar concepts like 'sessions,' 'interviews,' 'save_answers' vs. 'save_answer_set', "get_saved_interview_list" vs. "get_saved_answer_set_list," "load_interview_answers" vs. "load_answer_set."

In particular, it may help clear up confusion with four similar-sounding parameters in get_saved_interview_list:

filename_to_exclude (str, optional): The filename to exclude from the results. Defaults to "". exclude_current_filename (bool, optional): Whether to exclude the current filename from the results. Defaults to True. exclude_filenames (Optional[List[str]], optional): A list of filenames to exclude from the results. Defaults to None. exclude_newly_started_sessions (bool, optional): Whether to exclude sessions that are still on "step 1". Defaults to False.

aidanielson commented 1 month ago

here is GPT-created code that definitely needs to be refactored and harmonized with sessions.py, but might be a helpful starting place. `import datetime from typing import Union, Set, List, Optional, Dict, Any

def save_interview_answers( filename: str = al_session_store_default_filename, variables_to_filter: Union[Set[str], List[str], None] = None, metadata: Optional[Dict[str, Any]] = None, metadata_key_name: str = "metadata", original_interview_filename: Optional[str] = None, source_filename: Optional[str] = None, source_session: Optional[str] = None, additional_variables_to_filter: Optional[Union[Set[str], List[str]]] = None, overwrite: bool = False, custom_title: Optional[str] = None, ) -> str: """ Saves or overwrites a session with a specified interview filename.

Args:
    filename (str, optional): The desired filename for the new session. Defaults to `al_session_store_default_filename`.
    variables_to_filter (Union[Set[str], List[str], None], optional): The "base" list or set of variables to filter out. Defaults to `al_sessions_variables_to_remove`.
    metadata (Optional[Dict[str, Any]], optional): Dictionary containing metadata. Defaults to an empty dictionary.
    metadata_key_name (str, optional): Key name for metadata storage. Defaults to "metadata".
    original_interview_filename (str, optional): Original filename of the interview. Defaults to None.
    source_filename (str, optional): Source filename to get session variables from. Defaults to None.
    source_session (str, optional): Session ID of the source file. Defaults to None.
    additional_variables_to_filter (Union[Set[str], List[str], None], optional): List or set of additional variables to filter out. Defaults to None.
    overwrite (bool, optional): Determines if the session should overwrite an existing session. Defaults to False.
    custom_title (Optional[str], optional): Custom title for the session. Defaults to None.

Returns:
    str: ID of the new session.
"""
# Avoid using mutable default parameter
if not variables_to_filter:
    variables_to_filter = al_sessions_variables_to_remove
if not additional_variables_to_filter:
    additional_variables_to_filter = []
if not metadata:
    metadata = {}

# Get session variables, from either the specified or current session
all_vars = get_filtered_session_variables(
    filename=source_filename,
    session_id=source_session,
    variables_to_filter=variables_to_filter,
    additional_variables_to_filter=additional_variables_to_filter,
)

try:
    metadata["steps"] = (
        all_variables(include_internal=True).get("_internal").get("steps", -1)
    )
except:
    metadata["steps"] = -1

if original_interview_filename:
    metadata["original_interview_filename"] = original_interview_filename
else:
    metadata["original_interview_filename"] = all_variables(special="metadata").get(
        "title", user_info().filename.replace(":", " ").replace(".", " ")
    )
metadata["answer_count"] = len(all_vars)

if overwrite:
    existing_sessions = get_saved_interview_list(filename=filename, user_id=user_info().id)
    for session in existing_sessions:
        if session.get("title") == (custom_title or existing_title):
            # Update session with new data
            set_session_variables(filename, session["key"], all_vars, overwrite=True)
            set_interview_metadata(filename, session["key"], metadata, metadata_key_name=metadata_key_name)
            print("Session updated successfully.")
            return session["key"]

# Create a new session
current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
new_title = custom_title or f"{existing_title} ({current_datetime})"
metadata["title"] = new_title

new_session_id = create_session(filename)

# Copy in the variables from the specified session
set_session_variables(filename, new_session_id, all_vars, overwrite=True)

# Add the metadata
set_interview_metadata(
    filename, new_session_id, metadata, metadata_key_name=metadata_key_name
)

# Make the title display as the subtitle on the "My interviews" page
if metadata.get("title"):
    try:
        set_session_variables(
            filename,
            new_session_id,
            {"_internal['subtitle']": metadata.get("title")},
            overwrite=True,
        )
    except:
        log(
            f"Unable to set internal interview subtitle for session {filename}:{new_session_id} with name {metadata.get('title')}"
        )

return new_session_id

Example usage

overwrite_decision = False # Set to True to overwrite, False to save as a new session custom_session_title = "Custom Interview Title" # Optional custom title save_interview_answers("Sample Interview Title", overwrite=overwrite_decision, custom_title=custom_session_title) `