Implements the approved plan to replace the broken regex/index-master extraction with an LLM-subagent pipeline. Four parallel lanes: Lane A — scripts/extract_common.py (PDF/docx/doc/pptx/html/zip, no max_pages truncation), normalize_sources.py, chunk_sources.py (~20pg chunks + overlap, manifest registry), activity_schema.json. Lane B — app/config_taxonomy.py (16 fixed category slugs), schema rebuilt from scratch in app/models/ with content_type, language, source_files, source_excerpt, normalized_name, extraction_confidence, needs_review; FTS5 + 3 triggers extended with materials_list and skills_developed. Lane C — build_database.py (--rebuild, atomic swap, schema + fuzzy source_excerpt validation, dedup with needs_review band), validate_extractions.py, review_queue.py, new run_extraction.py orchestrator, SUBAGENT_PROMPT.md. Lane D — search.py content_type/language filters (default search excludes non-game content), E7 schema-compat audit; fixed a NULL keywords AttributeError in _boost_search_relevance. Removes 8 orphaned/dead scripts and app/services/parser.py + indexer.py. Adds tests/ (70 passing, 1 skipped — libreoffice absent). Note: Lane D made one additive edit to app/models/database.py (_update_category_counts) to surface content_type/language in get_filter_options, outside its nominal lane boundary but after Lane B completed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
177 lines
7.4 KiB
HTML
177 lines
7.4 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Căutare Activități - INDEX Sistem Jocuri{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="search-page">
|
|
<div class="search-header">
|
|
<h2 class="search-title">Căutare Activități Educaționale</h2>
|
|
<p class="search-subtitle">
|
|
Descoperă activități pentru copii și tineri din catalogul nostru de
|
|
{% if stats and stats.total_activities %}{{ stats.total_activities }}{% else %}500+{% endif %}
|
|
jocuri și exerciții.
|
|
</p>
|
|
</div>
|
|
|
|
<form method="POST" action="{{ url_for('main.search') }}" class="search-form">
|
|
<!-- Main search input -->
|
|
<div class="search-input-group">
|
|
<input
|
|
type="text"
|
|
name="search_query"
|
|
id="search_query"
|
|
class="search-input"
|
|
placeholder="Caută activități după nume, descriere sau cuvinte cheie..."
|
|
autocomplete="off"
|
|
>
|
|
<button type="submit" class="search-button">Căutare</button>
|
|
</div>
|
|
|
|
<!-- Dynamic filters -->
|
|
<div class="filters-grid">
|
|
{% if filters %}
|
|
{% if filters.category %}
|
|
<div class="filter-group">
|
|
<label for="category" class="filter-label">Categorie</label>
|
|
<select name="category" id="category" class="filter-select">
|
|
<option value="">Toate categoriile</option>
|
|
{% for category in filters.category %}
|
|
<option value="{{ category }}">{{ display_names.get(category, category) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if filters.content_type %}
|
|
<div class="filter-group">
|
|
<label for="content_type" class="filter-label">Tip conținut</label>
|
|
<select name="content_type" id="content_type" class="filter-select">
|
|
<option value="">Doar jocuri și activități</option>
|
|
{% for content_type in filters.content_type %}
|
|
<option value="{{ content_type }}">{{ display_names.get(content_type, content_type) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if filters.language %}
|
|
<div class="filter-group">
|
|
<label for="language" class="filter-label">Limbă</label>
|
|
<select name="language" id="language" class="filter-select">
|
|
<option value="">Toate limbile</option>
|
|
{% for language in filters.language %}
|
|
<option value="{{ language }}">{{ display_names.get(language, language) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if filters.age_group %}
|
|
<div class="filter-group">
|
|
<label for="age_group" class="filter-label">Grupa de vârstă</label>
|
|
<select name="age_group" id="age_group" class="filter-select">
|
|
<option value="">Toate vârstele</option>
|
|
{% for age_group in filters.age_group %}
|
|
<option value="{{ age_group }}">{{ age_group }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if filters.participants %}
|
|
<div class="filter-group">
|
|
<label for="participants" class="filter-label">Participanți</label>
|
|
<select name="participants" id="participants" class="filter-select">
|
|
<option value="">Orice număr</option>
|
|
{% for participants in filters.participants %}
|
|
<option value="{{ participants }}">{{ participants }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if filters.duration %}
|
|
<div class="filter-group">
|
|
<label for="duration" class="filter-label">Durata</label>
|
|
<select name="duration" id="duration" class="filter-select">
|
|
<option value="">Orice durată</option>
|
|
{% for duration in filters.duration %}
|
|
<option value="{{ duration }}">{{ duration }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if filters.materials %}
|
|
<div class="filter-group">
|
|
<label for="materials" class="filter-label">Materiale</label>
|
|
<select name="materials" id="materials" class="filter-select">
|
|
<option value="">Orice materiale</option>
|
|
{% for materials in filters.materials %}
|
|
<option value="{{ materials }}">{{ materials }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if filters.difficulty %}
|
|
<div class="filter-group">
|
|
<label for="difficulty" class="filter-label">Dificultate</label>
|
|
<select name="difficulty" id="difficulty" class="filter-select">
|
|
<option value="">Orice nivel</option>
|
|
{% for difficulty in filters.difficulty %}
|
|
<option value="{{ difficulty }}">{{ difficulty }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Action buttons -->
|
|
<div class="search-actions">
|
|
<button type="submit" class="btn btn-primary">Aplică filtrele</button>
|
|
<button type="button" class="btn btn-secondary" onclick="clearFilters()">Resetează</button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Quick stats -->
|
|
{% if stats and stats.categories %}
|
|
<div class="quick-stats">
|
|
<h3 class="stats-title">Categorii disponibile</h3>
|
|
<div class="stats-grid">
|
|
{% for category, count in stats.categories.items() %}
|
|
<div class="stat-item">
|
|
<span class="stat-label">{{ category }}</span>
|
|
<span class="stat-value">{{ count }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function clearFilters() {
|
|
// Reset all form fields
|
|
document.getElementById('search_query').value = '';
|
|
|
|
const selects = document.querySelectorAll('.filter-select');
|
|
selects.forEach(select => select.selectedIndex = 0);
|
|
}
|
|
|
|
// Auto-submit on filter change for better UX
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const filterSelects = document.querySelectorAll('.filter-select');
|
|
filterSelects.forEach(select => {
|
|
select.addEventListener('change', function() {
|
|
if (this.value) {
|
|
document.querySelector('.search-form').submit();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |