Extraction finished (575/588 chunks; 6 content-filter-blocked, 7 await re-extraction). DB rebuilt and frozen at 9418 activities — content_keys are now stable for the enrichment overlay. Part A (plumbing + UI): - database.py: name_ro/description_ro/rules_ro/variations_ro, indoor_outdoor, space_needed, estimated_fields, source_id/source_ids/chunk_key columns; FTS5 indexes the 4 *_ro columns across CREATE + all 3 triggers; new equality filters + category counts for both axes. - activity.py: new fields + bilingual display helpers (get_display_*, is_estimated, axis displays). - config_taxonomy.py: INDOOR_OUTDOOR/SPACE_NEEDED enums + normalizers (None on unrecognised, no fabrication). - search.py / routes.py / config.py / templates / css: new dropdowns, RO-primary rendering with "(estimat)" markers and collapsible original text, and a /source/<id> download route shipped DARK behind SOURCE_DOWNLOAD_ENABLED (copyright opt-in). - build_database.py: source_id/chunk_key in dict_to_activity; merge_cluster unions source_ids without touching enrichment fields. Part B (enrichment pipeline, built not yet run): - build_database.py: load_enrichment + apply_enrichment (post-dedup, keyed on content_key) + --enrichment CLI + stated-vs-estimated QA. - run_enrichment.py (resumable, --source/--limit pilot scoping, --collect), ENRICHMENT_PROMPT.md. Repair: scripts/repair_extractions.py fixes the subagents' systematic unescaped-ASCII-quote bug with a faithful char-scanner (escapes, never truncates) + schema validation + a strictly-more-text guard. json_repair was tried first, truncated silently, and is NOT used. build_database has no repair dependency. Tests: tests/test_enrichment.py added; 99 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
201 lines
8.5 KiB
HTML
201 lines
8.5 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 %}
|
|
|
|
{% if filters.indoor_outdoor %}
|
|
<div class="filter-group">
|
|
<label for="indoor_outdoor" class="filter-label">Interior / exterior</label>
|
|
<select name="indoor_outdoor" id="indoor_outdoor" class="filter-select">
|
|
<option value="">Oriunde</option>
|
|
{% for io in filters.indoor_outdoor %}
|
|
<option value="{{ io }}">{{ display_names.get(io, io) }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if filters.space_needed %}
|
|
<div class="filter-group">
|
|
<label for="space_needed" class="filter-label">Spațiu necesar</label>
|
|
<select name="space_needed" id="space_needed" class="filter-select">
|
|
<option value="">Orice spațiu</option>
|
|
{% for sp in filters.space_needed %}
|
|
<option value="{{ sp }}">{{ display_names.get(sp, sp) }}</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 %} |