Spaces:
No application file
No application file
| import json | |
| import streamlit as st | |
| import re | |
| from datetime import datetime, date, timedelta | |
| import base64 | |
| from template_base import DATA_INPUT_OUTPUT_TS, EVALUATION_METRIC_FIELDS, LEARNING_ARCHITECTURE, TASK_METRIC_MAP | |
| def get_base64_image(path): | |
| with open(path, "rb") as f: | |
| return base64.b64encode(f.read()).decode() | |
| def generate_date_options(start_year=1970, end_year=None): | |
| if end_year is None: | |
| end_year = datetime.today().year | |
| start = date(start_year, 1, 1) | |
| end = datetime.today().date() | |
| delta = (end - start).days | |
| return [start + timedelta(days=i) for i in range(delta + 1)] | |
| def require_task(): | |
| if "task" not in st.session_state: | |
| from app import task_selector_page | |
| st.session_state.runpage = task_selector_page | |
| st.rerun() | |
| # This avoids reloading on every rerun | |
| def get_model_card_schema(): | |
| with open("model_card_schema.json", "r") as f: | |
| return json.load(f) | |
| def store_value(key): | |
| st.session_state[key] = st.session_state["_" + key] | |
| def load_value(key, default=None): | |
| if key not in st.session_state: | |
| st.session_state[key] = default | |
| st.session_state["_" + key] = st.session_state[key] | |
| def validate_static_fields(schema, session_state, current_task): | |
| from template_base import DATA_INPUT_OUTPUT_TS | |
| missing = [] | |
| def is_empty(value): | |
| return value in ("", None, [], {}) | |
| skip_fields = set(DATA_INPUT_OUTPUT_TS.keys()) | |
| skip_keys = {"input_content_rtstruct_subtype", "output_content_rtstruct_subtype"} # puedes agregar más claves aquí | |
| skip_sections = {"evaluation_data_methodology_results_commisioning", "learning_architecture"} | |
| for section, fields in schema.items(): | |
| if section in skip_sections: | |
| continue | |
| if not isinstance(fields, dict): | |
| continue | |
| for key, props in fields.items(): | |
| if key in skip_fields and section in ["training_data_methodology_results_commisioning", "evaluation_data_methodology_results_commisioning"]: | |
| continue | |
| for key, props in fields.items(): | |
| if key in skip_keys: | |
| continue | |
| full_key = f"{section}_{key}" | |
| if props.get("required", False): | |
| model_types = props.get("model_types") | |
| if model_types is None or (current_task and current_task in model_types): | |
| value = session_state.get(full_key) | |
| if is_empty(value): | |
| label = props.get("label", key) or key.replace("_", " ").title() | |
| missing.append((section, label)) | |
| return missing | |
| def validate_learning_architectures(schema, session_state): | |
| missing = [] | |
| def is_empty(value): | |
| return value in ("", None, [], {}) | |
| forms = session_state.get("learning_architecture_forms", {}) | |
| schema_fields = schema.get("learning_architecture", {}) | |
| for i in range(len(forms)): | |
| prefix = f"learning_architecture_{i}_" | |
| for field in LEARNING_ARCHITECTURE: | |
| props = schema_fields.get(field) | |
| if not props: | |
| continue | |
| if props.get("required", False): | |
| full_key = f"{prefix}{field}" | |
| value = session_state.get(full_key) | |
| if is_empty(value): | |
| label = props.get("label", field.replace("_", " ").title()) | |
| missing.append(( | |
| "learning_architecture", | |
| f"{label} (Learning Architecture {i+1})" | |
| )) | |
| return missing | |
| def validate_modalities_fields(schema, session_state, current_task): | |
| missing = [] | |
| def is_empty(value): | |
| return value in ("", None, [], {}) | |
| modalities = [] | |
| for key, value in session_state.items(): | |
| if key.endswith("model_inputs") and isinstance(value, list): | |
| for item in value: | |
| modalities.append((item, "model_inputs")) | |
| elif key.endswith("model_outputs") and isinstance(value, list): | |
| for item in value: | |
| modalities.append((item, "model_outputs")) | |
| for modality, source in modalities: | |
| clean = modality.strip().replace(" ", "_").lower() | |
| prefix_train = f"training_data_{clean}_{source}_" | |
| for field, label in DATA_INPUT_OUTPUT_TS.items(): | |
| full_key = f"{prefix_train}{field}" | |
| value = session_state.get(full_key) | |
| if is_empty(value): | |
| missing.append(( | |
| "training_data_methodology_results_commisioning", | |
| f"{label} ({modality} - {source})" | |
| )) | |
| # --- EVALUATION --- | |
| prefix_eval = f"evaluation_data_{clean}_{source}_" | |
| for field, label in DATA_INPUT_OUTPUT_TS.items(): | |
| full_key = f"{prefix_eval}{field}" | |
| value = session_state.get(full_key) | |
| if is_empty(value): | |
| missing.append(( | |
| "evaluation_data_methodology_results_commisioning", | |
| f"{label} ({modality} - {source})" | |
| )) | |
| return missing | |
| def validate_evaluation_forms(schema, session_state, current_task): | |
| missing = [] | |
| def is_empty(value): | |
| return value in ("", None, [], {}) | |
| eval_forms = session_state.get("evaluation_forms", []) | |
| eval_section = schema.get("evaluation_data_methodology_results_commisioning", {}) | |
| metric_fields = TASK_METRIC_MAP.get(current_task, []) | |
| # Recolectar todas las keys métricas válidas para la tarea actual | |
| metric_field_keys = set() | |
| for type_field in metric_fields: | |
| metric_field_keys.update(EVALUATION_METRIC_FIELDS.get(type_field, [])) | |
| for name in eval_forms: | |
| slug = name.replace(" ", "_") | |
| prefix = f"evaluation_{slug}_" | |
| approved_same_key = f"{prefix}evaluated_same_as_approved" | |
| approved_same = session_state.get(approved_same_key, False) | |
| # 🔹 Validación general (no métricas) | |
| for key, props in eval_section.items(): | |
| if key in metric_field_keys: | |
| continue # ⛔️ Este campo se valida como métrico más abajo | |
| if approved_same and key in ["evaluated_by_institution", "evaluated_by_contact_email"]: | |
| continue | |
| if props.get("required", False): | |
| model_types = props.get("model_types") | |
| if model_types is None or (current_task and current_task in model_types): | |
| value = session_state.get(f"{prefix}{key}") | |
| if is_empty(value): | |
| label = props.get("label", key) or key.replace("_", " ").title() | |
| missing.append(("evaluation_data_methodology_results_commisioning", f"{label} (Eval: {name})")) | |
| # 🔹 Validación específica de métricas | |
| for type_field in metric_fields: | |
| entry_list = session_state.get(f"{prefix}{type_field}_list", []) | |
| for metric_name in entry_list: | |
| metric_short = metric_name.split(" (")[0] | |
| metric_prefix = f"evaluation_{slug}.{metric_name}" | |
| for field_key in EVALUATION_METRIC_FIELDS.get(type_field, []): | |
| props = eval_section.get(field_key) | |
| if not props or not props.get("required", False): | |
| continue | |
| full_key = f"{metric_prefix}_{field_key}" | |
| value = session_state.get(full_key) | |
| if is_empty(value): | |
| label = props.get("label", field_key.replace("_", " ").title()) | |
| missing.append(( | |
| "evaluation_data_methodology_results_commisioning", | |
| f"{label} (Metric: {metric_short}, Eval: {name})" | |
| )) | |
| return missing | |
| def validate_required_fields(schema, session_state, current_task=None): | |
| missing_fields = [] | |
| missing_fields += validate_static_fields(schema, session_state, current_task) | |
| missing_fields += validate_learning_architectures(schema, session_state) | |
| missing_fields += validate_modalities_fields(schema, session_state, current_task) | |
| missing_fields += validate_evaluation_forms(schema, session_state, current_task) | |
| return missing_fields | |
| def is_yyyymmdd(s): | |
| return isinstance(s, str) and len(s) == 8 and s.isdigit() | |
| def to_date(s): | |
| try: | |
| return datetime.strptime(s, "%Y%m%d").date() | |
| except: | |
| return None | |
| def set_safe_date_field(base_key: str, yyyymmdd_string: str | None): | |
| """ | |
| Guarda de forma segura un campo de fecha en st.session_state: | |
| - Acepta string YYYYMMDD válida. | |
| - Guarda .date() en las claves de widget. | |
| - Deja None si el valor no es válido. | |
| """ | |
| widget_key = f"{base_key}_widget" | |
| raw_key = f"_{widget_key}" | |
| if is_yyyymmdd(yyyymmdd_string): | |
| parsed_date = to_date(yyyymmdd_string) | |
| else: | |
| parsed_date = None | |
| # Guardar para el widget | |
| st.session_state[base_key] = yyyymmdd_string if parsed_date else None | |
| st.session_state[widget_key] = parsed_date | |
| st.session_state[raw_key] = parsed_date | |
| def populate_session_state_from_json(data): | |
| if "task" in data: | |
| st.session_state["task"] = data["task"] | |
| for section, content in data.items(): | |
| if section == "learning_architectures": | |
| st.session_state["learning_architecture_forms"] = { | |
| f"Learning Architecture {i + 1}": {} for i in range(len(content)) | |
| } | |
| for i, arch in enumerate(content): | |
| prefix = f"learning_architecture_{i}_" | |
| for key, value in arch.items(): | |
| full_key = f"{prefix}{key}" | |
| st.session_state[full_key] = value | |
| elif section == "training_data": | |
| # Guarda los campos planos y listas | |
| for k, v in content.items(): | |
| full_key = f"{section}_{k}" | |
| if not isinstance(v, list): | |
| st.session_state[full_key] = v | |
| else: | |
| st.session_state[full_key] = v | |
| st.session_state[full_key + "_list"] = v | |
| # Maneja los campos técnicos de inputs/outputs | |
| ios = content.get("inputs_outputs_technical_specifications", []) | |
| for io in ios: | |
| clean = io["input_content"].strip().replace(" ", "_").lower() | |
| src = io["source"] | |
| for io_key, io_val in io.items(): | |
| if io_key not in ["input_content", "source"]: | |
| io_full_key = f"training_data_{clean}_{src}_{io_key}" | |
| st.session_state[io_full_key] = io_val | |
| st.session_state["_" + io_full_key] = io_val # <- Esto es CLAVE | |
| elif section == "evaluations": | |
| eval_names = [entry["name"] for entry in content] | |
| st.session_state["evaluation_forms"] = eval_names | |
| for entry in content: | |
| name = entry["name"].replace(" ", "_") | |
| prefix = f"evaluation_{name}_" | |
| for key, value in entry.items(): | |
| if key == "inputs_outputs_technical_specifications": | |
| for io in value: | |
| clean = io["input_content"].strip().replace(" ", "_").lower() | |
| src = io["source"] | |
| for io_key, io_val in io.items(): | |
| if io_key not in ["input_content", "source"]: | |
| io_full_key = f"{prefix}{clean}_{src}_{io_key}" | |
| st.session_state[io_full_key] = io_val | |
| st.session_state["_" + io_full_key] = io_val | |
| elif isinstance(value, list) and key.startswith("type_"): | |
| metric_names = [m["name"] for m in value] | |
| st.session_state[f"{prefix}{key}_list"] = metric_names | |
| st.session_state[f"{prefix}{key}"] = metric_names | |
| for metric in value: | |
| metric_prefix = f"evaluation_{name}.{metric['name']}" | |
| for m_field, m_val in metric.items(): | |
| if m_field != "name": | |
| st.session_state[f"{metric_prefix}_{m_field}"] = m_val | |
| elif is_yyyymmdd(value): | |
| date_obj = to_date(value) | |
| if date_obj: | |
| widget_key = f"{prefix}{key}_widget" | |
| st.session_state[widget_key] = date_obj | |
| st.session_state[f"_{widget_key}"] = date_obj | |
| st.session_state[f"{prefix}{key}"] = value | |
| else: | |
| st.session_state[f"{prefix}{key}"] = value | |
| else: | |
| st.session_state[f"{prefix}{key}"] = value | |
| elif isinstance(content, dict): | |
| for k, v in content.items(): | |
| full_key = f"{section}_{k}" | |
| st.session_state[full_key] = v | |
| if k.endswith("creation_date"): | |
| set_safe_date_field(full_key, v) | |
| if isinstance(v, list): | |
| st.session_state[full_key + "_list"] = v | |
| def light_header(text, size="16px", bottom_margin="1em"): | |
| st.markdown( | |
| f""" | |
| <div style='font-size: {size}; font-weight: normal; color: #444; margin-bottom: {bottom_margin};'> | |
| {text} | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def light_header_italics(text, size="16px", bottom_margin="1em"): | |
| st.markdown( | |
| f""" | |
| <div style='font-size: {size}; font-style: italic; font-weight: normal; color: #444; margin-bottom: {bottom_margin};'> | |
| {text} | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def title_header(text, size="1.2rem", bottom_margin="1em", top_margin="0.5em"): | |
| st.markdown( | |
| f""" | |
| <div style=' | |
| font-size: {size}; | |
| font-weight: 600; | |
| color: #333; | |
| margin-top: {top_margin}; | |
| margin-bottom: {bottom_margin}; | |
| '>{text}</div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def title_header_grey(text, size="1.3rem", bottom_margin="0.2em", top_margin="0.5em"): | |
| st.markdown( | |
| f""" | |
| <div style=' | |
| font-size: {size}; | |
| font-weight: 600; | |
| color: #6c757d; | |
| margin-top: {top_margin}; | |
| margin-bottom: {bottom_margin}; | |
| '>{text}</div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def title(text, size="2rem", bottom_margin="0.1em", top_margin="0.4em"): | |
| st.markdown( | |
| f""" | |
| <div style=' | |
| font-size: {size}; | |
| font-weight: 650; | |
| color: #222; | |
| margin-top: {top_margin}; | |
| margin-bottom: {bottom_margin}; | |
| text-align: justify; | |
| '>{text}</div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def subtitle(text, size="1.05rem", bottom_margin="0.8em", top_margin="0.2em"): | |
| st.markdown( | |
| f""" | |
| <div style=' | |
| font-size: {size}; | |
| font-weight: 400; | |
| color: #444; | |
| margin-top: {top_margin}; | |
| margin-bottom: {bottom_margin}; | |
| text-align: justify; | |
| '>{text}</div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def section_divider(): | |
| st.markdown( | |
| "<hr style='margin: 1.5em 0; border: none; border-top: 1px solid #ccc;'>", | |
| unsafe_allow_html=True, | |
| ) | |
| def strip_brackets(text): | |
| return re.sub(r"\s*\(.*?\)", "", text).strip() | |