This file records each meaningful implementation, fix, refactor, and experiment. It is intended to support later review, decision traceability, and project history reconstruction.
Use the template below for every new entry. Append new entries to the end of the file and keep earlier entries intact.
- Establish an append-only Markdown log for every meaningful implementation, fix, refactor, or experiment in the repo.
- Added an iteration logging requirement to
AGENTS.md. - Created
docs/iteration_log.mdwith a reusable entry template. - Added a short reminder to
README.mdso the rule is visible from the main project overview.
- The project needs a durable record of design decisions, implementation intent, and validation outcomes.
- A consistent log makes it easier to review regressions, reconstruct context, and understand why changes were made later.
- Verified that the new rule was appended without removing existing AGENTS content.
- Verified that the log file contains a title, description, and reusable template.
- Verified that the README addition is a minimal, non-disruptive note.
- The repository now has a clear, low-friction process for recording each iteration in Markdown.
- Use this file for the next meaningful change, and keep appending new entries instead of overwriting previous ones.
- Add a small, experimental query normalization / bilingual expansion layer for the existing 19 retrieval regression queries without touching embedding, indexing, reranking, or the production default retrieval path.
- Added
backend/app/retrieval/query_normalization.pywith a tiny explicit rule set for working student, internship, photovoltaic/solar/PV, Go/Golang, IoT/IIoT, DevOps/CI/CD/platform/Kubernetes, backend developer/engineer, and software developer/engineer variants. - Added
backend/scripts/experiment_query_normalization.pyto compare baseline vs normalized retrieval on the existing 19-query regression set and to emit experiment-ready JSON/Markdown outputs. - Kept the experiment isolated from production retrieval code paths.
- The current baseline shows both-hit candidate formation is the main retrieval weakness, especially for sparse and cross-lingual queries.
- A small, interpretable preprocessing layer is the lowest-risk way to test whether query-side expansion can improve both-hit formation without changing the underlying retrieval stack.
- Attempted to validate the new scripts with local Python tooling, but the host environment does not expose a working Python interpreter.
- Attempted to use Docker and WSL for execution, but Docker daemon access is denied and WSL service creation is blocked in this sandbox.
- Because execution was blocked, the experiment could not be run end-to-end and no new results files were generated yet.
- The implementation scaffold is in place, but the experiment remains unvalidated due environment restrictions rather than code defects.
- Re-run
backend/scripts/experiment_query_normalization.pyin an environment with a working Python runtime or Docker daemon, then persistdocs/query_normalization_experiment.mdandoutput/query_normalization_experiment.json.
- Turn the current working tree into a clean, versioned snapshot that can be committed and tagged without dragging in local build artifacts.
- Added ignore rules for local runtime environments, Node dependencies, Playwright caches, generated logs, smoke test artifacts, and other transient workspace outputs in
.gitignore. - Kept the existing code, planning, and documentation changes intact for the upcoming commit.
- The repository had meaningful implementation changes mixed with local-only artifacts such as
node_modules/,.venv311/, logs, screenshots, and ad hoc smoke-test files. - A version lock is more useful when the commit boundary excludes machine-specific noise and only captures durable project state.
- Reviewed the current
git statusand confirmed the workspace contains both tracked changes and untracked local artifacts. - Verified the ignore list now covers the most visible generated directories and files.
- The tree is better suited for a clean commit and future checkout because transient development outputs are no longer meant to be tracked.
- Stage the intended source, planning, and documentation changes, create the commit, and add a git tag for the locked snapshot.
- Verify whether the migrated Python environment is actually usable and determine whether the query normalization experiment can be executed in the current workspace.
- Performed a read-only environment check on
backend\.venv311\Scripts\python.exe. - Located the query normalization experiment script at
backend/scripts/experiment_query_normalization.py. - Checked whether
docs/query_normalization_experiment.mdandoutput/query_normalization_experiment.jsonalready exist and are non-empty.
- The repository is in retrieval quality hardening, so the immediate need was to verify runtime readiness rather than extend functionality.
- The migrated interpreter path was expected to replace the broken old environment, but that claim needed direct validation before any further work.
backend\.venv311\Scripts\python.exe -Vfailed with:Unable to create process using '"C:\Users\LIU\AppData\Local\Programs\Python\Python311\python.exe" -V'backend\.venv311\Scripts\python.exe -c "import sys; print(sys.executable)"failed with the same launcher error.where.exe pythonreturned no system Python installation.py -0preported:No installed Pythons found!- Verified that
backend/scripts/experiment_query_normalization.pyexists. - Verified that
docs/query_normalization_experiment.mdexists and is non-empty. - Verified that
output/query_normalization_experiment.jsonexists and is non-empty.
- The migrated
backend\.venv311interpreter is not currently runnable because it still points at a missing base Python 3.11 installation. - The experiment artifacts are already present on disk and non-empty, but this round did not successfully re-run the experiment, so the experiment cannot be marked complete.
- Restore a valid Python 3.11 base runtime for
backend\.venv311or otherwise repair the launcher target, then rerun the experiment and re-validate both artifacts before declaring success.
- Rebuild a working Python environment from scratch (Python 3.11.9 + new venv at
backend/.venv311), install all backend dependencies, and execute the query normalization experiment script to validate the bilingual term expansion layer against the 19 regression queries.
- Created
backend/.venv311usingpy -3.11 -m venv backend/.venv311. - Upgraded pip/setuptools/wheel in the new venv.
- Installed
insightflow-backendin editable mode viapip install -e backend/, pulling all dependencies frompyproject.toml(fastapi, faiss-cpu, httpx, datasets, pydantic, sqlalchemy, uvicorn, etc.). - Ran
backend/scripts/experiment_query_normalization.pyusingbackend/.venv311/Scripts/python.exe. - Verified both experiment artifacts were written:
output/query_normalization_experiment.json(1 197 707 bytes)docs/query_normalization_experiment.md(21 323 bytes)
- The old
backend/.venvwas corrupted because its base Python interpreter no longer existed, and files were locked by a running process, preventing deletion. - The previous iteration log entry recorded the experiment as "unvalidated due to environment restrictions." The environment needed to be rebuilt before the experiment could be run.
- All scope boundaries were respected: no embedding changes, no index rebuilds, no reranker changes, no frontend changes.
backend/.venv311/Scripts/python.exe -V→ Python 3.11.9 confirmed.import fastapi,import numpy,import faissall succeeded.from backend.app.main import appsucceeded from project root.- Script emitted both files without error: JSON at
output/, MD atdocs/. - Both output files are non-empty (JSON ~1.2 MB, MD ~21 KB).
- Environment reconstruction is complete and fully functional.
- Query normalization experiment ran end-to-end successfully and produced both expected artifacts.
- Review
docs/query_normalization_experiment.mdfor the A/B/C conclusion and the recommended next step from the experiment summary, then decide whether to adopt the bilingual expansion rules as a production retrieval preprocessing step.
- Confirm the
backend/.venv311environment is functional and that the query normalization experiment can be re-run successfully, producing fresh artifacts.
- Verified
py -0pshows Python 3.11.9 and 3.13.3 both available. - Confirmed
py -3.11 -VreturnsPython 3.11.9successfully. - Confirmed
backend/.venv311/Scripts/python.exe -Vworks (Python 3.11.9). - Ran
backend/scripts/experiment_query_normalization.pyusingbackend/.venv311/Scripts/python.exe. - Both artifacts overwritten with fresh timestamps:
output/query_normalization_experiment.json(1 197 707 bytes, mtime 2026-04-11T01:30:08)docs/query_normalization_experiment.md(21 323 bytes, mtime 2026-04-11T01:30:08)
- System reminders indicated possible environment instability; re-verified all steps end-to-end.
- Confirmed no old
backend/.venvfiles were copied or migrated — all dependencies resolved frompyproject.toml.
py -3.11 -V→ Python 3.11.9 confirmed.py -0p→ Python 3.11 and 3.13 both listed and functional.backend/.venv311/Scripts/python.exe -c "import fastapi,numpy,faiss,pydantic,httpx,sqlalchemy"→ all ok.from backend.app.main import app→ ok.- Both experiment artifacts regenerated and verified non-empty with current timestamps.
- Environment is fully functional and reproducible.
- Experiment re-run confirmed — both artifacts are fresh, not stale.
- Read
docs/query_normalization_experiment.mdfor the experiment conclusion (A/B/C grade and recommended next step), then decide whether the bilingual term expansion layer should be promoted to the production retrieval path.
- Close the decision loop on whether query normalization / bilingual term expansion should enter the formal retrieval preprocessing path in InsightFlow V1.
- Added a decision record only; no business logic, retrieval code, or frontend code was changed.
- Captured the current policy position for the query normalization candidate inside the project iteration log.
- The experiment showed clear upside in a subset of sparse and role-specific queries, but the gains are not yet stable enough for unconditional default-on rollout.
- Cross-lingual coverage remains weaker and sample-limited, and there is still localized drift risk in precision and source card focus.
- That makes the feature worth keeping as a gated capability, but not as a global preprocessing default.
- Reviewed the completed experiment artifacts and the decision analysis already produced from
docs/query_normalization_experiment.mdandoutput/query_normalization_experiment.json. - Confirmed the decision state is based on completed evidence, not on a new run or a new metric pass.
- Hold.
- Query normalization does not enter global retrieval preprocessing by default.
- The capability remains a gated option / candidate capability for controlled use.
- If this direction is revisited later, only validate controlled enablement.
- Prioritize broader cross-lingual samples.
- Keep watching both-hit formation, top-k focus, and source card drift.
- Do not turn it on globally unless the decision changes.
- Shrink query normalization from a full-candidate capability into an explicit
off | gated | onretrieval option, keep the default atoff, preserve normalization decision fields in retrieval trace, and re-run the 19-query regression comparison foroffvsgated.
- Reworked
backend/app/retrieval/query_normalization.pyinto an explicit normalization decision layer with:off,gated, andonmodes- explicit bilingual and technical alias families
- explainable matched-family and applied-decision outputs
- Threaded normalization mode through retrieval and summary request paths:
backend/app/schemas/retrieval.pybackend/app/schemas/summary.pybackend/app/retrieval/hybrid.pybackend/app/services/summarizer.pybackend/app/observability/trace_types.pybackend/app/observability/trace_builder.py
- Added retrieval/search response fields and retrieval trace fields for:
raw_querynormalized_querynormalization_appliedmatched_rule_familiesnormalization_mode
- Updated
backend/scripts/experiment_query_normalization.pyto compareoffvsgatedand target the new metrics:- both-hit delta
- top-k both-hit delta
- trigger coverage
- useful trigger rate
- harm rate
- source card drift count
- Added targeted retrieval tests in
backend/tests/test_retrieval_hybrid_api.pyfor gated normalization behavior and retrieval API normalization fields.
- The prior decision was Hold for global normalization rollout: local gains existed, but cross-lingual evidence was still thin and precision / source-card drift risk remained.
- That made a fully enabled default inappropriate, while still leaving room for a controlled gated option in the retrieval layer.
- The implementation therefore narrowed the capability to an explicit, reversible retrieval decision rather than a silent default behavior change.
- Static validation completed by re-reading all modified source files after patching:
backend/app/retrieval/query_normalization.pybackend/app/retrieval/hybrid.pybackend/app/services/summarizer.pybackend/scripts/experiment_query_normalization.pybackend/tests/test_retrieval_hybrid_api.py
- Attempted to run retrieval tests with
backend\.venv311\Scripts\python.exe, but execution failed with:Unable to create process using '"C:\Users\LIU\AppData\Local\Programs\Python\Python311\python.exe" ...'
- Verified the underlying launcher problem is coupled with sandbox restrictions:
- direct execution of
C:\Users\LIU\AppData\Local\Programs\Python\Python311\python.exereturnedAccess is denied - direct directory access / copy attempts against that runtime also returned
Access is denied - attempted hard-link creation into the workspace also failed with
Access is denied
- direct execution of
- Because of the runtime blocker, this round did not successfully run:
- pytest validation
- the updated
offvsgatedexperiment script - new experiment JSON / markdown artifact generation
- The gated normalization code path has been implemented, but this round is not complete.
- The hard requirement to run the real experiment and generate fresh artifacts was not met because Python execution is blocked in the current sandbox/runtime state.
- Default behavior remains safely aligned with the requested policy because the new normalization mode defaults to
off.
- Restore a Python execution path that is runnable inside the workspace sandbox, then:
- run the retrieval tests
- execute
backend/scripts/experiment_query_normalization.py - generate the new
offvsgatedreport and JSON artifacts - review the triggered-query drift cases before deciding whether
gatedis stable enough as a non-default option
- Run a precision-preserving retrieval hardening experiment that does not change query semantics and does not touch embeddings, reranker, frontend, or summary generation.
- Focus only on lightweight lexical retrieval changes, starting with phrase-aware plus field-aware keyword scoring, and compare the default baseline against a single explicit lexical hardening mode across the existing 19 regression queries.
- Added a new lexical retrieval mode scaffold in
backend/app/retrieval/keyword.py:offphrase_field_v1
- Implemented a minimal phrase-aware and field-aware lexical scoring layer on top of existing keyword retrieval:
- explicit preferred phrase list
- lightweight title / tags / location / chunk_type field bonuses
- no query expansion
- no synonym injection
- no embedding changes
- Kept the hardening precision-preserving by removing candidate-pool widening and limiting the lexical bonus to a small capped rescore.
- Threaded
lexical_hardening_modethrough retrieval and summary request/response paths:backend/app/schemas/retrieval.pybackend/app/schemas/summary.pybackend/app/retrieval/hybrid.pybackend/app/services/summarizer.pybackend/app/observability/trace_types.pybackend/app/observability/trace_builder.pybackend/app/api/retrieval.py
- Added / updated tests for the lexical hardening mode and trace propagation:
backend/tests/test_retrieval_hybrid_api.pybackend/tests/test_reranker_integration.py
- Added a new experiment script:
backend/scripts/experiment_lexical_hardening.py
- Generated new experiment artifacts:
output/lexical_hardening_experiment.jsondocs/lexical_hardening_experiment.md
- To unblock validation inside the sandbox, downloaded the official Python 3.11 embeddable runtime into the workspace and configured
python311._pthso the workspace-local interpreter can import the existing backend environment.
- Query normalization / expansion is now frozen as a non-mainline scaffold because source-card focus drift was too high.
- The next retrieval-quality lever needed to preserve original query meaning and instead improve lexical focus and phrase retention.
- Phrase-aware plus field-aware keyword scoring was chosen as the smallest explainable change that could be rolled back cleanly if it failed.
- The first scoring draft was too aggressive and accidentally expanded the effective candidate surface via an enlarged keyword pool; that behavior was removed to stay aligned with the "do not expand recall, preserve focus" goal.
- Verified the workspace-local embedded Python runtime runs successfully:
./python.exe -V->Python 3.11.9./python.exe -c "import sys, pytest; print(sys.version); print(pytest.__version__)"
- Ran targeted retrieval tests:
./python.exe -m pytest backend/tests/test_retrieval_hybrid_api.py -q->11 passed./python.exe -m pytest backend/tests/test_reranker_integration.py -q->29 passed
- Ran the lexical hardening experiment end-to-end:
./python.exe backend/scripts/experiment_lexical_hardening.py
- Verified fresh artifact generation:
output/lexical_hardening_experiment.jsondocs/lexical_hardening_experiment.md
- Current aggregate experiment result from the generated report:
- average both-hit delta:
0.000 - average top-k both-hit delta:
0.263 - average source-card focus delta:
0.013 - source-card drift count:
7 - improved queries:
0/19 - harmed queries:
8/19 - sparse subset top-k both-hit delta:
0.333
- average both-hit delta:
- The lightweight lexical hardening version is much safer than the first overly aggressive draft, but it still does not produce a clearly positive enough win to promote immediately.
- The experiment improved or preserved both-hit behavior on average without reopening query expansion, and sparse queries were slightly more stable, but source-card drift is still present in 7 of 19 queries and there are no clean "promote now" wins.
- This means the lexical hardening path remains a valid mainline investigation, but this specific
phrase_field_v1variant should stay experimental rather than replace the baseline today.
- Review the 7 drifted queries and classify whether the remaining drift mostly comes from:
- phrase bonuses on role titles
- title-vs-tags weighting
- over-sensitive source-card ordering on near-tie results
- If there is a clear dominant failure pattern, run one narrower follow-up iteration that further reduces drift without reintroducing candidate-pool expansion.
- Validate that the gated normalization implementation is runnable, that the retrieval/schema/trace paths are intact, and that the
offvsgatedexperiment produces fresh artifacts.
- No code changes; this was a pure validation pass.
- Installed
pytestintobackend/.venv311to enable test execution (pytest was in optional dev deps but not yet installed). - Ran all import checks for core modules, schema, trace, retrieval, and summarizer.
- Ran targeted pytest suite:
backend/tests/test_retrieval_hybrid_api.py(10 tests) andbackend/tests/test_summary_orchestration_api.py+backend/tests/test_summary_provider_and_schema.py(8 tests). - Executed
backend/scripts/experiment_query_normalization.pyto generate freshoffvsgatedexperiment artifacts.
- The prior round (17:10) was blocked by a runtime access issue and could not run pytest or the experiment script.
- This round confirms the implementation is fully executable and the default
offpolicy is upheld.
- All imports OK: fastapi, numpy, faiss, pydantic, httpx, sqlalchemy, app.main, query_normalization, retrieval/summary schemas, trace_types, hybrid retrieval, summarizer.
- Schema verification:
RetrievalSearchRequest.normalization_modepresent;SummaryRequest.normalization_modepresent;RetrievalTracecontains all 5 normalization fields (raw_query, normalized_query, normalization_applied, matched_rule_families, normalization_mode). test_retrieval_hybrid_api.py: 10/10 passed, includingtest_gated_normalization_only_applies_to_matched_familiesandtest_gated_normalization_stays_off_without_family_match.test_summary_orchestration_api.py+test_summary_provider_and_schema.py: 8/8 passed.- Experiment script produced fresh gated artifacts:
output/query_normalization_gated_experiment.json— 1 204 784 bytes, mtime 2026-04-11T02:14:15docs/query_normalization_gated_experiment.md— 19 171 bytes, mtime 2026-04-11T02:14:15
- Gated normalization implementation passes acceptance: all tests green, all schema fields present, experiment runs end-to-end.
- Default remains
offas required. - Experiment data shows gated mode triggers on 9/19 queries (47.4%) but has 88.9% harm rate (source card drift), confirming gated should remain a manual/opt-in option only.
- Python runtime blocker from the 17:10 entry is resolved.
- Review the 8 harmful trigger cases in
docs/query_normalization_gated_experiment.mdfor specific drift patterns, then decide whether to narrow the rule dictionary to only the 1 useful trigger (backend developer Go/Rust) or keep gated as an opt-in flag for advanced users.
- Run a stricter lexical precision control experiment that does not change query semantics and only touches lexical retrieval behavior.
- Focus on lowering source-card drift through field-aware weighting, phrase or proximity protection, minimum-match style lexical constraint, and score decomposition.
- Compare the current default baseline against a new explicit lexical mode across the existing 19 regression queries.
- Extended lexical retrieval mode support to include
precision_control_v1while preserving existingoffandphrase_field_v1modes. - Implemented a stricter keyword scoring path in
backend/app/retrieval/keyword.py:- stronger title / tags / chunk_type-aware lexical weighting
- exact phrase and short-gap proximity bonuses for original query phrases
- minimum-match style lexical gate to suppress weakly anchored keyword candidates
- explicit lexical score decomposition:
keyword_base_scorekeyword_phrase_bonuskeyword_field_bonuskeyword_min_match_penaltykeyword_score
- Propagated lexical score decomposition through retrieval result and trace structures:
backend/app/retrieval/hybrid.pybackend/app/schemas/retrieval.pybackend/app/schemas/summary.pybackend/app/observability/trace_types.pybackend/app/observability/trace_builder.py
- Added and updated tests to cover the new lexical mode and decomposition fields:
backend/tests/test_retrieval_hybrid_api.pybackend/tests/test_reranker_integration.pybackend/tests/test_retrieval_embeddings_and_vector_index.py
- Added a new experiment script and generated fresh artifacts:
backend/scripts/experiment_lexical_precision_control.pyoutput/lexical_precision_control_experiment.jsondocs/lexical_precision_control_experiment.md
- Query expansion and normalization are already frozen as non-mainline scaffolds because they caused too much source-card focus drift.
- The next retrieval hardening attempt needed to preserve raw query meaning and instead make lexical matching stricter and more explainable.
- This round intentionally prioritized precision over recall, so any improvement had to come from better lexical focus rather than broader candidate coverage.
- Ran targeted retrieval tests:
./python.exe -m pytest backend/tests/test_retrieval_hybrid_api.py -q->12 passed./python.exe -m pytest backend/tests/test_reranker_integration.py -q->29 passed./python.exe -m pytest backend/tests/test_retrieval_embeddings_and_vector_index.py -q->8 passed
- Ran the precision-control experiment end-to-end:
./python.exe backend/scripts/experiment_lexical_precision_control.py
- Verified fresh artifact generation:
output/lexical_precision_control_experiment.jsondocs/lexical_precision_control_experiment.md
- Aggregate experiment result from the generated report:
- average both-hit delta:
-3.632 - average top-k both-hit delta:
-2.263 - average source-card focus delta:
0.011 - average role-boundary precision delta:
-0.012 - average adjacent-job noise delta:
0.002 - source-card drift count:
12 - improved queries:
0/19 - harmed queries:
12/19
- average both-hit delta:
- This round did not pass.
precision_control_v1does not enter the default mainline.- The default switch does not change and remains
off. - Although the experiment produced a tiny positive average focus-score delta, it materially worsened both-hit coverage, slightly worsened role-boundary precision, and increased source-card drift to 12 of 19 queries.
- The minimum-match style lexical control is therefore too destructive in its current form to serve as the next mainline retrieval hardening step.
- Use the new lexical score decomposition output to classify the 12 drifted or degraded queries by failure mode:
- over-strict minimum-match pruning
- phrase/proximity bonus reordering near-tie candidates
- field-weighting bias that overpromotes generic titles
- If one dominant failure mode explains most of the harm, run one narrow rollback-style iteration on that single component; otherwise freeze this lexical precision-control branch as experimental scaffold only.
- Formally close the query-side lexical tuning line (phrase_field_v1) and redirect主线 to document-side mismatch auditing as the next stable retrieval precision lever.
- No code changes; this is a decision-only entry.
- Documented the final ruling on phrase_field_v1: Keep-Experimental-Only, not promoted to baseline.
- Default retrieval baseline remains unchanged (lexical_hardening_mode: off).
- The query normalization scaffold (gated / off) remains intact as a non-default capability.
- Frozen experiment lines: query normalization gated, lexical hardening phrase_field_v1.
- Main line exits query-side lexical tuning and query-side expansion tuning.
- phrase_field_v1 produced 0/19 clean wins, 8/19 harmed queries, and 7/19 source-card drift cases in the 19-query regression set.
- Query normalization gated had 88.9% harm rate on triggered queries (8/9) in the gated experiment.
- Both query-side directions (expansion normalization and lexical hardening) have now independently converged on the same failure mode: source-card focus drift.
- This pattern suggests the underlying instability is not in the query preprocessing layer but in the document retrieval surface itself — specifically, how documents are surfaced relative to the true information need.
- Document-side mismatch auditing is the structurally correct next lever because it addresses the root cause of why near-miss documents appear in the top-k in the first place.
- Based on completed experiment artifacts already on disk:
docs/lexical_hardening_experiment.mddocs/query_normalization_gated_experiment.mdoutput/lexical_hardening_experiment.jsonoutput/query_normalization_gated_experiment.json
- No new execution required; decision is a policy ruling based on existing evidence.
- phrase_field_v1: Keep-Experimental-Only. Does not enter default main retrieval链路.
- Default baseline: unchanged, lexical_hardening_mode = off.
- Query normalization scaffold: gated capability only, default off.
- Experiment line: frozen as scaffold.
- Main line: exits query-side lexical tuning, pivots to document-side mismatch auditing.
- Run a document-side mismatch audit on the existing 19-query regression set, targeting role, employment-type, seniority, domain, and location mismatches between the retrieved documents and the query intent.
- Use existing trace and retrieval metadata to identify structural reasons for noisy source cards rather than further query-side tuning.
- Extract role_family, employment_type, and seniority from existing source card title+tag metadata via rule-based inference, run a structured mismatch audit on all 19 regression queries, and determine whether document-side metadata hardening is the right next main retrieval precision lever.
- No code changes; post-hoc analysis only.
- Ran rule-based metadata extraction on all 76 source cards (top-4 per query, 19 queries) from the lexical hardening baseline.
- Derived role_family via ordered title keyword matching (first-match wins) with tag fallback — 67/76 docs (88%) extract successfully.
- Derived employment_type via tag-precedence then title keyword — only 13/76 docs (17%) extract to non-unknown; 63/76 are unknown. Critical metadata coverage gap, not a rule deficiency.
- Derived seniority via tag-precedence then title keyword — 37/76 docs (49%) extract to non-unknown.
- Audited role mismatch, employment-type mismatch, seniority mismatch per query.
- Generated artifacts:
output/document_side_metadata_audit.jsondocs/document_side_metadata_audit.md
- Prior title-only inference confirmed role boundary confusion is dominant but needed formal extraction with coverage quantification.
- Employment-type was suspected to be underdetected; this round quantifies the coverage gap precisely.
- Seniority was suspected to be secondary; this round confirms it is narrowly scoped to junior-vs-senior queries only.
- Source:
output/lexical_hardening_experiment.jsonbaseline (19 queries, top-4 source cards each = 76 documents). - Additional reference:
output/query_normalization_gated_experiment.jsonandoutput/lexical_precision_control_experiment.jsonfor drift query cross-reference. - No new retrieval, no code changes, no re-indexing.
- Role boundary confusion is the dominant structural problem. 10/19 queries have detectable role mismatch; 6/7 drift queries have role mismatch. The specific failure: query targets Role A → system returns adjacent Role B due to keyword overlap (data_engineer ↔ data_analyst, backend_developer → full_stack, frontend → full_stack).
- Employment-type contamination is real but metadata-invisible. Only 17% document coverage means 83% of docs bypass any employment-type filter. Unpaid Mandatory Internship cards appearing in IoT/typescript queries cannot be caught without an explicit employment_type schema field.
- Seniority mismatch is secondary and narrowly scoped. Only 1/19 queries shows seniority mismatch (junior query returning senior positions); the same query also has role mismatch, so seniority is not an independent primary driver.
- Role-boundary metadata-aware filter is the correct next mainline investment. Employment-type filtering requires schema-level work first.
- dagster exception: 1 drift query has zero detectable role/emp/seniority mismatch — noise source is chunk-level keyword proximity, not metadata mismatch. Separate failure mode.
- Extract employment_type as an explicit schema field from title patterns (Werkstudent/Working Student → working_student, Unpaid/Praktikum/Internship → internship, Full-time → full_time), then prototype two lightweight post-retrieval components on the 19-query traces:
- A role-family ranker that boosts/demotes documents by role_family intersection with query role signals
- An employment-type filter that demotes internship/unpaid documents when query has no internship/junior constraint (only on docs with extractable signals)
- If role-family ranker reduces drift in ≥ 4/7 drift queries without increasing it elsewhere, promote it as the next mainline retrieval hardening step.
- If employment-type coverage remains below 50% after extraction, deprioritize employment-type filtering until schema-level work is done.
- Prototype post-retrieval, pre-rerank role_family-aware soft demotion to reduce source-card drift without changing query semantics. Based on the document-side audit conclusion that role boundary confusion is the dominant structural problem.
- Created
backend/app/retrieval/role_family.py:extract_role_family(title, tags_json)— ordered keyword matching on title, tag fallback, first match winsextract_query_role_intent(query)— exact keyword match from query string, no expansionADJACENT_ROLE_FAMILIESadjacency map derived from audit observationscompute_role_demotion()— returns factor (0.7 for adjacent, 1.0 otherwise)apply_role_demotion()— applies factor to merged_score, re-sorts
- Created
backend/scripts/experiment_role_family_demotion.py— baseline vs experiment on 19 queries - Added optional role-family trace fields to
RetrievalEvidenceResultschema (backward-compatible) - Added vector timeout fallback to keyword-only when embedding provider is unreachable
- Generated
output/role_family_demotion_experiment.jsonanddocs/role_family_demotion_experiment.md
- The document-side audit identified role boundary confusion as the primary structural cause of noisy source cards. A post-retrieval demotion is the minimal invasive change to address this without rebuilding the pipeline.
- 19 regression queries, baseline vs experiment (both with normalization=off, lexical_hardening=off)
- Metric thresholds: improved ≥ 4 queries AND drift count < 3 to pass
- Result: improved=12, harmed=6, drift=6 — did not pass thresholds
- Prototype does not pass. Default switch stays
off. - Drift count = 6, exceeding the < 3 threshold. Harmed queries (6):
machine learning engineer tensorflow pytorch,backend developer Go Rust job requirements,junior software developer Python internship,AWS cloud DevOps remote contract,clickhouse snowflake dbt data engineer,Praktikum data science Munich. - Root cause of failure: adjacency map is wrong in both directions.
cloud_engineer↔devops_engineeradjacency causes Cloud Engineers (correct for "AWS cloud DevOps") to be demoted and Solutions Architects (worse) to replace them.backend_engineer↔backend_developeradjacency demotes near-identical backend roles.ai_engineer↔ml_engineeradjacency demotes correct AI Engineer docs for ML Engineer queries. The adjacency map was derived from observed baseline returns, not from explicit role taxonomy — it captures "these roles appeared together in baseline" not "these roles are truly adjacent." - The 0.7 factor is too aggressive — causes demoted docs to fall below non-demoted docs with equal/close scores, creating unnecessary ranking disorder.
- The "improved" count (12) is misleading: most improved queries had no explicit role intent, so the demotion was a no-op and the unchanged ranking was counted as "improved."
- Do NOT iterate on the demotion factor or threshold. The adjacency map itself is the bottleneck.
- Before any further prototype work, invest in a correct role taxonomy and adjacency map derived from the actual job schema (not from baseline returns). Specifically:
- Define explicit role equivalence classes (backend_developer ≈ backend_engineer, software_developer ≈ software_engineer, etc.)
- Define asymmetric adjacency only where truly harmful: e.g., full_stack is adjacent to backend_only (not the reverse), data_analyst is adjacent to data_engineer but data_engineer is NOT adjacent to data_analyst
- Run a new audit to re-derive the adjacency map from confirmed mismatch cases only
- Alternatively: abandon role-boundary filtering and focus on the dagster-type failure mode (drift with zero detectable metadata mismatch) — chunk-level keyword proximity is a separate noise channel that role filtering cannot address
- Audit the currently available job-posting corpus to determine whether it is sufficient for the current InsightFlow retrieval stage: benchmark v1, hybrid retrieval comparison, mismatch or drift analysis, and document-side metadata refinement planning.
- Performed a read-only audit against
data/processed/insightflow.db. - Verified the current on-disk corpus size and found a state mismatch with the originally stated 89-record assumption: the current main SQLite corpus contains 195
job_contentsrows, while the eval copy contains 118. - Read project planning documents, retrieval evaluation docs, schema, the current role-family extraction module, and the v0 evaluation query set to ground the audit in the active project stage.
- Computed corpus distributions using lightweight title/tag/location-based rules for:
- role family
- employment type
- seniority
- geography / work mode
- skill clusters
- Reviewed hard-negative availability by checking whether adjacent role or constraint pairs exist in enough quantity to support retrieval-noise analysis.
- The project has already moved past “can the system run” and is now making decisions about benchmark coverage, retrieval comparisons, and document-side metadata hardening.
- Those decisions depend on corpus shape, not just corpus size, so the audit needed to identify where the current dataset is representative enough for MVP work and where it is too skewed for stronger conclusions.
- Confirmed current corpus counts directly from SQLite:
data/processed/insightflow.db: 195job_contents, 1235job_chunksbackend/data/processed/insightflow.db: 195job_contents, 1235job_chunksbackend/data_copy_for_eval/processed/insightflow.db: 118job_contents, 713job_chunks
- Verified current evaluation fixture shape from
backend/data/eval_queries_v0.jsonandbackend/docs/evaluation_v0.md. - Used only existing stored document fields (
title,employment_type,location_text,tags_json,description) plus explicit lightweight audit rules; no retrieval reruns, re-indexing, or code-path changes.
- The current corpus is large enough to support MVP-stage retrieval plumbing checks, benchmark v1 scaffolding, and metadata-hardening prioritization.
- It is not balanced enough to support strong generalized conclusions about job-retrieval quality because role coverage is highly skewed, Germany dominates geography, employment metadata is sparse, and data-analytics hard negatives remain too thin.
- The immediate value is diagnostic rather than definitive: the corpus is good enough to tell the team what to fix next, but not good enough to declare the retrieval direction stable.
- Prioritize targeted corpus augmentation for underrepresented adjacent role families and employment constraints so the benchmark can expose precision failures on realistic near-miss jobs instead of mostly broad-category noise.
- Turn the dataset coverage audit into a concrete 20-30 high-value job supplementation plan that improves benchmark usefulness, hard-negative density, and metadata refinement value without changing retrieval or running new experiments.
- Produced a role-prioritized supplementation design focused on the current corpus weaknesses:
- data analyst
- data engineer
- ML/AI
- DevOps / cloud / platform
- small boundary slices for database admin and frontend / UX
- Defined intended mixes for employment type, seniority, geography, and work mode rather than only role counts.
- Defined explicit hard-negative pairs the new samples should create on purpose so the benchmark can better expose role-boundary and constraint-boundary errors.
- Added sampling constraints and a minimal field spec for new postings so future supplements preserve title, location, role, and employment signals needed by document-side metadata extraction.
- The current 195-row corpus is enough for MVP validation but still too skewed for stronger retrieval-route conclusions.
- The main blocker is not raw volume; it is the lack of dense adjacent-role and adjacent-constraint examples in the exact areas the benchmark is trying to judge.
- A small but intentional supplement is therefore higher leverage than broad undirected corpus growth.
- Plan grounded in the completed dataset audit and current corpus counts already verified from SQLite.
- No retrieval runs, no indexing work, no code changes, and no fabricated data were introduced.
- The fastest path from "MVP-capable" toward "more stable retrieval judgment" is a narrow supplementation round centered on hard negatives, not a general corpus expansion round.
- The highest-value additions are analyst vs engineer, ML/AI vs data engineer, and DevOps vs cloud/platform, with explicit junior/full-time/internship/remote contrasts.
- Freeze a concrete target quota sheet and start collecting against those quotas instead of continuing broad unsupervised ingestion.
- Convert the supplementation plan into a concrete 30-job execution spec with fixed quotas, collection rules, required fields, checklist items, and stop conditions.
- Defined a final 30-job quota split with role-prioritized allocation rather than even expansion.
- Added explicit target mixes for employment type, seniority, geography, and work mode.
- Specified hard-negative pairings the collection pass must intentionally create.
- Defined collection-time acceptance and rejection rules so the sampling round improves benchmark value instead of just increasing volume.
- Added stop conditions that depend on structural coverage, not just hitting a raw row count.
- The current corpus already supports MVP validation, so the next data round must maximize retrieval-route signal rather than corpus size.
- A fixed execution spec prevents the supplementation pass from drifting back into Berlin-heavy, senior-heavy, full-time-heavy generic collection.
- Built directly from the completed dataset audit and targeted supplementation plan; no retrieval runs, no code changes, and no synthetic data generation.
- The 30-job round should be treated as a constrained balancing pass focused on high-value hard negatives and metadata clarity.
- Success depends on fulfilling the quota structure and pair coverage, not merely collecting any 30 additional postings.
- Materialize the quota sheet and use it as the only intake target for the next collection pass.
- Turn the already approved supplementation plan into concrete execution artifacts: a fixed sampling spec, a quota JSON, a tracking sheet template, and an iteration log entry.
- Created
docs/sampling_execution_spec_v1.mdas the executable supplementation spec. - Created
output/supplementation_quota_v1.jsonwith the structured final quota. - Created
output/supplementation_tracking_sheet_v1.csvas a collection template. - Applied the required minimum arithmetic corrections only:
- changed the
devops / cloud / platformemployment mix from1 working_student + 3 full_time + 2 contractto1 working_student + 4 full_time + 1 contract - fixed the
frontend / ux_design boundaryseniority tostudent
- changed the
- The plan was already decided, but it still needed executable artifacts before any directed collection could begin.
- The quota arithmetic needed to be consistent so future sampling can be tracked against one stable source of truth.
- No retrieval logic, embedding logic, indexing, reranking, frontend, or summary code was changed.
- Verified the corrected horizontal employment totals are now:
- internship = 5
- working_student = 5
- full_time = 17
- contract = 3
- Verified the corrected horizontal seniority totals are now:
- junior_entry = 7
- mid = 10
- senior = 8
- student = 4
- lead = 1
- Verified the tracking sheet is a template only and does not contain fabricated supplementation results.
- The supplementation execution spec is now grounded in concrete files and can be used immediately for collection.
- The quota arithmetic is corrected without changing the main role-family allocation strategy.
- Start directed collection against the fixed quota sheet, beginning with the analyst, engineer, ML/AI, and devops-cloud-platform groups.
- Prepare the first two directed supplementation candidate pools:
data_analystanddata_engineer, with enough over-selection to support later screening into the tracking sheet.
- Built a read-only candidate pool from currently accessible public job sources, focusing on:
- data analyst / analytics / BI / business-analytics roles
- data engineer / analytics engineer / analytical data engineer roles
- Kept the candidate pools larger than the final quota so later screening can remove weak-fit or duplicate items.
- Prioritized candidates that help cover employment type, seniority, geography, work mode, and hard-negative structure.
- Did not modify retrieval, indexing, embeddings, reranking, frontend, or summary behavior.
- The supplementation spec is already fixed; the next practical step is to prepare real candidate pools that can be filtered into the tracking sheet.
- Over-selecting candidates before acceptance reduces the chance that the final 8+8 role quotas get filled with low-signal or overly repetitive jobs.
- No retrieval run was executed.
- No code paths were changed.
- Candidate pools were built from visible, concrete job postings rather than fabricated rows.
- The output of this round is a screening pool, not a claim that supplementation intake is complete.
- The analyst and data engineer candidate pools are now large enough to support the next screening step into the tracking sheet.
- Additional curation is still required before these candidates become accepted supplementation rows.
- Screen the analyst and data engineer candidate pools against the quota sheet and move accepted rows into the tracking sheet.
- Move the analyst and data engineer candidate pools into the supplementation tracking sheet and assign first-pass provisional screening decisions.
- Replaced the placeholder tracking sheet with concrete analyst and data engineer candidate rows.
- Added per-candidate screening fields including:
- quota bucket
- normalized role family
- employment type
- seniority
- country, city, and work mode
- hard-negative pair
- duplicate check
- signal quality check
- provisional accepted / hold / reject status
- Marked a recommended analyst set of 8 provisional accepts.
- Marked a recommended engineer set of 7 provisional accepts.
- Preserved the explicit engineer internship gap instead of forcing a false completion.
- The supplementation plan is only actionable once candidates are turned into a traceable screening sheet.
- A provisional pass helps preserve the best coverage candidates while keeping weaker or redundant rows visible for backup use.
- No retrieval logic was changed.
- No retrieval run or new experiment was executed.
- No synthetic job postings were invented; only concrete candidate rows were used.
- The engineer group remains intentionally incomplete on the internship dimension, as required.
- The analyst pool is now screened tightly enough to support an 8-row provisional accepted set.
- The engineer pool is screened tightly enough to support a 7-row provisional accepted set, but still needs one explicit internship-style data engineer candidate before that quota can be treated as complete.
- Find one high-signal data engineer internship candidate and then run the next screening pass to close the engineer slot.
- Close the explicit data engineer internship gap and finalize the first engineer accepted set of 8.
- Added two real internship-style data engineering candidates to the tracking sheet:
E14Data Engineer Intern - EquativE15Praktikum Data Engineering (m/w/d) - AXA Konzern AG
- Marked
E14as the internship candidate that closes the engineer gap. - Marked
E15as a high-quality backup hold candidate. - Finalized an engineer accepted set of 8 based on role clarity, coverage value, and gap closure.
- The previous screening pass intentionally stopped at 7 accepted engineer rows because the internship dimension was still missing.
- That gap had to be filled before the engineer group could be treated as structurally complete for v1 supplementation intake.
- No retrieval logic was changed.
- No retrieval run or experiment was executed.
- No synthetic postings were introduced.
- The internship gap is now explicitly covered by a real data-engineering internship candidate with strong platform/pipeline signals.
- The engineer group now has a final recommended accepted set of 8.
- The internship gap is filled.
- One additional internship-style engineer row remains in hold status as a backup candidate.
- Freeze the engineer accepted set and move on to the next role group screening pass.
- Build the first ml_ai candidate pool, write it into the tracking sheet, and assign provisional accepted / hold / reject decisions.
- Added 12 ml_ai candidate rows to the supplementation tracking sheet.
- Covered the target role signals with concrete titles including:
- Machine Learning Engineer Intern
- Working Student Agentic AI & Automation
- AI Engineer
- AI Engineer (LLMs + Knowledge Graphs)
- Senior AI/ML Engineer
- Senior Generative AI Engineer
- Machine Learning Scientist
- Marked a provisional accepted set of 6 that best fits the current quota shape.
- Preserved additional strong ml_ai rows in hold status for backup use rather than overfilling senior Germany full-time coverage.
- After freezing analyst and data engineer accepted sets, the next supplementation priority is ml_ai.
- Writing the candidate pool into the tracking sheet creates a concrete screening state instead of leaving the role group as an informal list.
- No retrieval logic was changed.
- No retrieval run or new experiment was executed.
- No synthetic job data was invented.
- The ml_ai role group now has a candidate pool large enough for tracking-sheet screening.
- The ml_ai candidate pool is sufficient to enter tracking-sheet screening.
- The provisional accepted set of 6 is structurally usable, but the group still has one soft tension: the internship and working-student requirements both naturally pull toward student-like seniority, so the quota fit is achieved by mapping the internship role as junior-entry.
- Freeze the provisional ml_ai accepted set unless a stronger non-Berlin or non-Germany mid-level replacement appears.
- Remove the quota-driven seniority distortion in the ml_ai accepted set and restate the recommendation using only semantically correct labels.
- Corrected
M01fromjunior_entrytostudentin the tracking sheet because it is an internship role and does not carry an explicit junior-entry title signal. - Kept
M12as the truejunior_entrybackup candidate because its title explicitly saysJunior AI Engineer. - Did not change retrieval logic, candidate sourcing, or the broader supplementation structure.
- The previous ml_ai provisional set was operationally convenient but semantically impure.
- That kind of relabeling would contaminate later metadata, benchmark, and mismatch-audit interpretation.
- No new experiment was run.
- No code was changed.
- The only update was semantic cleanup in the tracking sheet plus clarification of how the ml_ai quota should be interpreted.
- The remaining tension is structural: the ml_ai quota asks for both internship/working-student coverage and a separate junior-entry slot, but realistic ml_ai candidate pools often concentrate early-career roles into internship and student formats.
- The ml_ai labels are now semantically clean.
- The ml_ai accepted recommendation can be frozen if the quota is interpreted minimally as requiring early-career coverage rather than forcing both
studentandjunior_entryto appear simultaneously inside the final six. - If strict independent
junior_entrypresence is still required, then the smallest blocker is the need to swap inM12for one mid-level full-time role.
- Freeze ml_ai with semantically correct labels under the early-career coverage interpretation, or explicitly approve the stricter junior-entry requirement and replace one mid-level accepted row with
M12.
- Formally document the ml_ai quota interpretation adjustment, freeze the accepted 6, and mark the true junior-entry row as backup.
- Added the ml_ai seniority interpretation rule to
docs/sampling_execution_spec_v1.md. - Added the same bucket-specific interpretation to
output/supplementation_quota_v1.json. - Updated
output/supplementation_tracking_sheet_v1.csvto:- freeze
M01throughM06as accepted - mark
M12as backup - preserve semantically correct seniority labels without relabeling internship as junior-entry
- freeze
- The candidate pool was already sufficient, but the quota interpretation needed to be explicit so the accepted set could be frozen without semantic distortion.
- This keeps the supplementation artifacts aligned with later metadata and benchmark work.
- No code was changed.
- No retrieval run or new experiment was executed.
- The ml_ai interpretation rule is now documented in both the human-readable spec and the structured quota JSON.
- The tracking sheet now distinguishes accepted rows from the junior-entry backup row.
- The ml_ai accepted 6 is now frozen.
M12is explicitly marked as the junior-entry backup.- The quota interpretation cleanup is fully documented and no longer depends on implicit discussion context.
- Move on to the next role bucket screening pass with the ml_ai bucket treated as frozen.
- Build the first devops / cloud / platform candidate pool, write it into the tracking sheet, and assign provisional accepted / hold / reject decisions.
- Added 12 devops/cloud/platform candidate rows to the supplementation tracking sheet.
- Covered the target title families with concrete postings including:
- DevOps Engineer
- Site Reliability Engineer
- Cloud Engineer
- Junior Platform Engineer
- Teamlead IT-Infrastructure
- Marked a provisional accepted set of 6.
- Preserved additional strong rows in hold status where they improve backups but would overconcentrate Berlin/Germany senior full-time coverage if accepted immediately.
- After freezing analyst, data_engineer, and ml_ai, the next supplementation priority is devops/cloud/platform.
- This role bucket needs real overlap between cloud, platform, and devops vocabulary so later retrieval checks can expose near-miss behavior.
- No retrieval logic was changed.
- No retrieval run or new experiment was executed.
- No synthetic job data was invented.
- The candidate pool is large enough for tracking-sheet screening.
- The devops/cloud/platform candidate pool is sufficient to enter tracking-sheet screening.
- A provisional accepted set of 6 can be formed now.
- There is one remaining structural tension: the seniority sub-quota sums to 7 labels across only 6 accepted slots, so the current accepted set optimizes employment, geography, and hard-negative coverage first and leaves one seniority preference as a soft gap rather than forcing semantic distortion.
- Freeze the provisional accepted set unless a clearly stronger non-Germany onsite senior or lead cloud/platform candidate appears.
2026-04-11 23:30 - DevOps / Cloud / Platform Quota Interpretation Cleanup and Freeze Readiness Check
- Formally document the devops/cloud/platform quota interpretation adjustment and decide whether the current accepted 6 can be frozen without semantic distortion.
- Added the devops/cloud/platform seniority interpretation rule to
docs/sampling_execution_spec_v1.md. - Added the same bucket-specific interpretation to
output/supplementation_quota_v1.json. - Updated
output/supplementation_tracking_sheet_v1.csvto freeze the current accepted 6:D01D03D04D05D07D10
- The candidate pool was already sufficient, but the raw seniority sub-quota was internally tighter than the six available accepted slots.
- A minimal interpretation rule was needed to avoid forcing unnatural relabeling or unnecessary reshuffling.
- No code was changed.
- No retrieval run or new experiment was executed.
- No synthetic job data was invented.
- The interpretation rule is now documented in both the human-readable spec and the structured quota JSON.
- The tracking sheet now marks the accepted 6 as frozen rather than provisional.
- The devops/cloud/platform accepted 6 can now be frozen.
- The remaining tension has been converted from an implicit conflict into an explicit, documented bucket-specific interpretation rule.
- There is no remaining sourcing blocker for this bucket.
- Move on to the final remaining bucket or final supplementation review with the devops/cloud/platform bucket treated as frozen.
- Close the last two supplementation slots with one database_admin boundary sample and one frontend/UX boundary sample, completing the accepted 30-sample supplementation set.
- Added and accepted
B01as the finaldatabase_adminboundary sample:- Oracle Database Administrator (m/f/d) - Indra Avitech GmbH
- Added and accepted
B02as the finalfrontend_ux_boundarysample:- Frontend Software Engineer Working Student - QuantCo
- Wrote both rows into
output/supplementation_tracking_sheet_v1.csv.
- The first four role groups were already frozen, leaving only the two boundary samples needed to close the supplementation plan.
- These final two rows were selected for strong role clarity and boundary value rather than broad category expansion.
- No code was changed.
- No retrieval run or new experiment was executed.
- No synthetic postings were invented.
- Both final rows carry clear role signals and fit their intended boundary buckets:
- DBA vs data-engineer/platform
- frontend vs UX/web/product
- Both final boundary rows are accepted.
- The full 30-sample supplementation set is now complete and closed at the tracking-sheet level.
- Run the final supplementation completeness review and summarize the accepted 30-row set by bucket and coverage.
- Review the full accepted supplementation set for completeness against the execution spec and confirm whether it is ready for benchmark-phase use.
- Audited the accepted supplementation rows against:
- role-family quotas
- employment-type coverage
- seniority coverage, including bucket-specific interpretations
- geography and work-mode coverage
- hard-negative coverage
- signal quality and duplicate-risk flags
- Verified that the intended accepted set reaches the planned 30 rows across all role buckets.
- Identified one administrative inconsistency in the tracking sheet: the frozen analyst and data-engineer rows still carry
provisional_yesinstead ofaccepted, even though the supplementation decisions already treat them as frozen.
- The supplementation pass only becomes truly usable for the next benchmark phase when coverage is complete and any remaining blockers are clearly distinguished as structural or merely administrative.
- No code was changed.
- No retrieval run or new experiment was executed.
- No new samples were added.
- Verified target bucket counts are satisfied at the decision level:
- analyst 8
- data_engineer 8
- ml_ai 6
- devops_cloud_platform 6
- database_admin 1
- frontend_ux_boundary 1
- Verified the only remaining inconsistency is status-label bookkeeping in the CSV, not supplementation coverage.
- The accepted 30-sample supplementation set is complete at the decision level and is ready for benchmark-phase use.
- The only remaining caveat is administrative: some frozen rows still need their tracking-sheet status normalized from
provisional_yestoaccepted.
- Normalize the remaining frozen tracking-sheet statuses and then treat the accepted 30-row set as the benchmark-phase supplementation corpus.
- Finalize the supplementation freeze by normalizing all frozen rows in the tracking sheet to
acceptedand confirming the accepted set is ready for benchmark-phase use.
- Updated all frozen analyst and data-engineer rows from
provisional_yestoacceptedinoutput/supplementation_tracking_sheet_v1.csv. - Left backup, hold, and reject rows unchanged.
- Added
docs/supplementation_freeze_review.mdto summarize the final accepted set and its readiness state.
- The supplementation decisions were already complete, but the ledger still mixed frozen rows with provisional status labels.
- Finalizing those labels removes the last administrative blocker before using the accepted set as benchmark-phase augmentation data.
- No retrieval logic, benchmark logic, or application code was changed.
- No retrieval run or new experiment was executed.
- Confirmed the final state counts are:
- accepted = 30
- backup = 1
- hold = 16
- reject = 7
- Confirmed the accepted bucket totals remain unchanged and sum to 30.
- The supplementation freeze is finalized.
- The accepted 30-sample set is now formally synchronized in the tracking sheet and can be used as the benchmark-phase enhancement corpus.
- Start the next benchmark / hybrid evaluation pass using the finalized accepted 30-sample supplementation set.
- Audit the repository before a GitHub push for unnecessary files and possible secret exposure, then refresh the root README so the public project description is accurate and easy to understand.
- Reviewed project context, roadmap, current state, tracked files, ignored files, and current worktree status.
- Scanned tracked and visible repository files for common secret patterns, private-key markers, and token-like strings.
- Updated
.gitignoreto block root-level portable Python/runtime artifacts that were still appearing as untracked candidates for commit. - Rewrote the root
README.mdinto a concise public-facing project overview with scope, stack, quick start, API surface, provider configuration, and project status.
- The repository had several local runtime artifacts in the root directory that are not source code and are easy to accidentally commit during a manual GitHub push.
- The previous README was too long, mixed internal notes with public-facing documentation, and contained visible encoding noise in several sections, which would weaken the first impression of the repo.
- A release-readiness pass reduces the chance of pushing noisy files or leaking environment-specific information.
- Reviewed
git status --shortandgit status --ignored --shortto distinguish tracked work, ignored runtime output, and still-exposed root artifacts. - Confirmed the newly added ignore rules now cover the root portable-runtime patterns that were previously not ignored.
- Ran pattern-based secret checks across tracked files and non-ignored workspace files.
- No concrete live API keys or private keys were found in the scanned repository content.
- Did observe example placeholders and documentation references to environment variable names such as
CHAT_API_KEYandEMBEDDING_API_KEY; these appear to be examples rather than leaked secrets. - Did not run the full backend/frontend test suites in this audit pass because the task focus was repository hygiene and documentation readiness.
- No obvious credential leak was found in the repository content scanned during this pass.
- The main pre-push risk was accidental inclusion of local portable Python/runtime files from the repo root;
.gitignorehas now been tightened to reduce that risk. - The repository still has several untracked docs and local experiment artifacts that should be intentionally reviewed before pushing, rather than committed by default.
- Review the remaining untracked documentation files and decide which ones are worth publishing in the first GitHub push versus keeping local.
- Run one final
git statuscheck before commit/push and verify that only intentional source changes and selected documentation remain.
- Convert the refreshed root README into a concise bilingual Chinese/English version suitable for a public GitHub repository.
- Replaced the single-language public README with a bilingual structure.
- Added mirrored Chinese and English sections for project overview, scope, stack, quick start, provider configuration, testing, design principles, and project status.
- Kept the README focused on externally useful onboarding information instead of internal experiment details.
- The repository owner is preparing a GitHub push and asked for a bilingual presentation.
- A bilingual README improves accessibility for both Chinese-speaking reviewers and broader public readers without forcing them into internal planning documents first.
- Keeping the root README concise helps the repository make a stronger first impression.
- Reviewed the updated README content for structure consistency across both languages.
- Confirmed the commands and referenced endpoints remain aligned with the current repository layout and backend/frontend entrypoints.
- Did not run application tests because this iteration was documentation-only.
- The repository now has a bilingual root README that is much better suited for public presentation than the previous internal-heavy version.
- Make a final inclusion decision on untracked benchmark and design docs before creating the Git commit for GitHub.
- Prepare a proper open-source project license and a first-release note draft suitable for the upcoming GitHub publication.
- Added a root-level
LICENSEfile using the MIT License template. - Added
docs/release_notes_v0.1.0.mdwith bilingual release copy for the first public version. - Updated the root
README.mdto link to the project license and the first release notes.
- The repository previously only had an untracked
LICENSE.txtfrom the bundled Python runtime, which is not the correct project-level open-source license. - A public GitHub push benefits from having explicit reuse terms and a clean release narrative ready at the same time.
- MIT is a pragmatic default for a personal/public engineering project that aims to stay easy to fork, review, and extend.
- Confirmed the new license is placed at the repository root in the conventional
LICENSEpath. - Confirmed the release-notes document is linked from the root README.
- Did not run code tests because this iteration only added documentation and repository metadata.
- The repository now has the minimum open-source publication materials expected for a public GitHub release: a project license and an initial release note.
- Decide which code and documentation files belong in the first public commit, then stage and publish the curated set.
- Register the two nucleus-ready strict remote/location queries into the benchmark assets and rerun
hybridvshybrid_plus_remote_location_refinement_v1.2only on those two queries.
- Added two new query definitions to
backend/data/eval_queries_v0.json:strict_remote_analytics_or_analyst_requirementsstrict_germany_remote_or_hybrid_requirements
- Updated
output/benchmark_query_buckets_v1.jsonto:- increase the source query count from
25to27 - register both query ids under
strict_metadata_constraint - add both query ids to
nucleus_expansion_candidateswith rationale and acceptance notes
- increase the source query count from
- Generated
output/strict_remote_queries_rerun.json - Added
docs/benchmark_run_record_v1_strict_remote_queries.md - Added
docs/strict_remote_query_candidates_v1.md
remote_data_requirementshad already been shown to be a poor primary acceptance query for remote/location refinement because it behaves like a broad summary prompt with metadata preference.- The benchmark needed stricter, more interpretable acceptance prompts before the remote/location refinement direction could be judged fairly.
- No retrieval architecture changes were made.
- No full benchmark rerun was performed.
- Only the two strict remote/location queries were rerun.
- Results:
- subset size:
2 - top-1 changes vs
hybrid:0 / 2 - hybrid top-1 metadata satisfaction:
2 / 2 - refined top-1 metadata satisfaction:
2 / 2 - source-card drift count:
0 / 2
- subset size:
- Interpretation:
- the new queries are more diagnostic and more interpretable than
remote_data_requirements - but this rerun did not produce new top-1 gains because plain
hybridalready satisfied the constraints on both strict prompts
- the new queries are more diagnostic and more interpretable than
- The benchmark expansion is valid and useful: these two strict queries are better remote/location acceptance prompts than the previous broad summary query.
- But the rerun still does not provide new evidence strong enough to promote remote/location refinement, because
hybridalready returns metadata-satisfying top-1 results on both strict queries.
- Keep the two strict queries in nucleus expansion and use them as the primary acceptance slice for the next remote/location refinement change, rather than using broad summary prompts.
- Formally close the current remote/location refinement branch and record its state as parked rather than promoted.
- Added
docs/remote_location_refinement_closure_v1.md. - Consolidated the branch outcome into a single closure note covering:
- what was validated
- what was fixed
- what still did not improve
- why the branch is parked
- reopen conditions
- next mainline focus
- The branch had accumulated enough experiments, diagnostics, and benchmark corrections that its current state needed to be made explicit.
- Without a closure note, future work would risk revisiting the same remote/location loop without a clear statement of what was already learned and why the line was not promoted.
- No code was changed.
- No retrieval experiment was run.
- No benchmark rerun was performed.
- The closure note is consistent with the documented evidence already in the repository:
- grouped
work_modeexposure was successfully implemented and verified - strict remote/location acceptance queries were registered and rerun
- the strict rerun still showed insufficient promotion evidence because
hybridalready satisfied the main metadata intent at top-1
- grouped
- Remote/location refinement is not a failed branch.
- The grouped signal exposure fix was successful.
- The benchmark correction toward strict acceptance queries was also correct.
- But the branch still lacks enough incremental evidence to be promoted.
- Status:
Parked, not promoted.
- Leave the branch parked unless one of the documented reopen conditions is met, and keep the mainline focus on broader benchmark and retrieval work.
- Run a cleaner remote/location-only refinement pass on top of
hybridand determine whether this narrower branch is ready to become a retrieval-line candidate.
- Executed a real benchmark comparison between
hybridandhybrid_plus_remote_location_refinement_v1.1on the locked 19-query nucleus. - Restricted refinement to the explicit remote/location subset only:
remote_data_requirementsremote_analytics_expectationsenglish_speaking_jobs_germanyremote_vs_onsite_expectations
- Generated
output/hybrid_plus_remote_location_refinement_v1_1.json. - Added:
docs/benchmark_run_record_v1_hybrid_plus_remote_location_refinement_v1_1.mddocs/benchmark_run_summary_v1_remote_location_addendum.md
- The previous mixed metadata refinement pass suggested the remote/location branch was more promising than the role-boundary branch.
- This round was meant to isolate that branch and verify whether the positive signal still holds when role-family logic is removed entirely.
- No retrieval architecture, embedding/index pipeline, or summary logic was changed.
- No new dataset supplementation work was performed.
- The run used the locked benchmark snapshot and the same 19-query nucleus.
- Subset metrics for the remote/location slice were:
- subset size =
4 - top-1 changes =
0 / 4 - metadata top-1 satisfied count:
hybrid = 2refined = 2
- metadata top-3 satisfied count:
hybrid = 2refined = 2
- source-card drift count =
0 / 4 - outside-subset collateral harm =
0
- subset size =
- The remote/location-only branch is safe and interpretable, but this clean v1.1 pass did not produce enough gain to justify promotion.
- With only
4triggered queries and no top-1 or metadata-satisfaction lift, the right conclusion is evidence-insufficient rather than success.
- Run a focused diagnostic pass on
remote_data_requirementsandremote_analytics_expectationsto determine whether the blocker is weak document-side metadata or too-conservative adjustment strength.
- Determine whether the weak remote/location refinement result is driven mainly by:
- candidate scarcity
- retrieval head miss
- or extraction weakness
- Audited the live SQLite corpus for explicit
remote/hybrid/onsite_or_unspecifiedsignals across the target role buckets. - Compared signal provenance by field:
location_texttitledescription
- Cross-checked the two failing remote queries against the stored
hybridandhybrid_plus_remote_location_refinement_v1_1outputs. - Checked the accepted supplementation overlay separately to distinguish live-corpus scarcity from not-yet-ingested benchmark augmentation data.
- The previous diagnosis showed that remote refinement was not moving rankings, but the project still needed to know whether the blocker was:
- lack of viable remote candidates
- candidates existing but failing to enter the retrieved head set
- or candidates entering the head set but being mislabeled as onsite
- No code, retrieval logic, or benchmark configuration was changed.
- No new ranking experiment was run.
- Live main-corpus findings:
data_analyst:0 remote,0 hybrid,1 onsite_or_unspecifieddata_engineer:0 remote,1 hybrid,1 onsite_or_unspecifiedml_ai:3 remote,2 hybrid,3 onsite_or_unspecifieddevops_cloud_platform:6 remote,1 hybrid,2 onsite_or_unspecified
- For the target buckets in the live main corpus, recoverable remote/hybrid signals came from:
description: yestitle: nolocation_text: no
- In the two failing remote queries, top-5 grouped candidates under the stored run contained
0cards labeled asremoteorhybrid. - But at least one relevant head candidate,
Data Engineer / Analytics Engineer (w/m/d), contains an explicit3/2 hybrid in Berlinsignal in the document description, meaning the stored v1.1 head-level work-mode inference missed a real hybrid clue.
- The dominant blocking pattern is mixed:
- candidate scarcity in the live main corpus for remote data/analytics roles
- plus extraction weakness because the usable remote/hybrid clues often live only in
description
- Retrieval-head miss is secondary, not primary:
- at least one relevant hybrid candidate already enters the head set
- but its work-mode signal is not carried into the head-level refinement features
- The best classification is:
- extraction weakness + candidate scarcity
- with retrieval-head miss as a smaller contributor
- Inspect whether the retrieval/refinement path can safely expose description-derived remote/hybrid evidence at ranking time before any further weight tuning is considered.
- Trace where remote/hybrid clues are lost between raw documents and refinement-time ranking features for:
remote_data_requirementsremote_analytics_expectations
- Inspected the retrieval schema and grouping path in:
backend/app/schemas/retrieval.pybackend/app/retrieval/hybrid.py
- Re-read the stored v1.1 remote-location experiment output.
- Queried the live retrieval response for the two remote queries and inspected grouped evidence chunks for top candidates.
- Verified whether known candidates such as
Data Engineer / Analytics Engineer (w/m/d)andWerkstudent (m/w/d) Python / Data & Energy Analyticsalready carry remote/hybrid clues inside their retrieved evidence.
- Earlier diagnosis showed the main issue was extraction weakness, but the project still needed to know the exact layer where the signal stops being available to ranking-time refinement.
- That distinction matters because the smallest safe fix is different if the signal is missing from:
- document ingestion
- chunk/schema exposure
- grouped candidate metadata
- or refinement wiring
- No code or retrieval behavior was changed.
- No new ranking experiment or weight-tuning pass was run.
- Confirmed that:
- raw
job_contentsrows still contain the description text job_chunks.chunk_textpreserves description-derived cluesRetrievalEvidenceResultpreserveschunk_textGroupedJobResultpreservesevidence
- raw
- Confirmed that relevant remote/hybrid clues do appear in retrieved evidence:
Data Engineer / Analytics Engineer (w/m/d)containswe work 3/2 hybrid in BerlinWerkstudent (m/w/d) Python / Data & Energy AnalyticscontainsTags: [\"IT\", \"Remote\", \"Working student\"]in a retrievedtitle_plus_summarychunk
- Confirmed that the stored v1.1 refinement input still labeled those candidates as
onsite_or_unspecified
- The remote/hybrid clue is not primarily lost in raw document storage or chunk retrieval.
- It is lost at ranking-time exposure:
- the clue survives into retrieved evidence
- but is not promoted into a stable grouped/source-card metadata field that refinement actually consumes
- The minimal repair target is therefore the grouped-candidate / refinement-input boundary, not weight tuning.
- Design the smallest metadata exposure change that promotes description-derived or summary-chunk-derived work-mode clues into refinement-visible grouped candidate metadata before any new remote/location refinement run is attempted.
- Write the minimal design spec for exposing evidence-level remote/hybrid clues as grouped-candidate
work_modemetadata and wiring that field into refinement input.
- Created
docs/work_mode_exposure_design_v1.md. - Documented:
- current signal flow
- grouped-layer metadata gap
- proposed minimal field additions
- source-priority order
- explicit extraction rules
- grouped exposure plan
- refinement-input wiring plan
- validation approach before implementation
- Previous audits showed that remote/hybrid clues already survive into retrieved evidence, but they do not become stable grouped-candidate metadata.
- The project needed a minimal design target before any implementation or further refinement experiment could be justified.
- No code was changed.
- No new retrieval experiment was run.
- The design was grounded in the existing retrieval schema, grouped-candidate structure, and the known false-negative cases from the remote-signal audits.
- The spec explicitly rejects vague wording and limits promotion to explicit remote/hybrid clues.
- The smallest meaningful repair should happen at the grouped-candidate exposure layer.
- The key additions are:
work_modework_mode_signal_source
- Weight tuning should remain deferred until those fields are available to refinement.
- Convert the design spec into a small implementation plan for grouped
work_modeexposure and refinement-input wiring.
- Implement the smallest runnable version of grouped-candidate
work_modeexposure and make that metadata available for refinement input, without changing weights or expanding scope.
- Added grouped-level
work_modeandwork_mode_signal_sourcetobackend/app/schemas/retrieval.py. - Added
backend/app/retrieval/work_mode.pyto derive explicit remote/hybrid signals from:titlelocation_texttagstitle_plus_summaryevidence_chunk_text
- Updated
backend/app/retrieval/hybrid.pyso grouped candidates derive and expose work-mode metadata duringgroup_by_job(...). - Added
docs/work_mode_exposure_implementation_notes_v1.md.
- Prior audits showed that remote/hybrid clues were already present in retrieved evidence but were not promoted into stable grouped metadata.
- The smallest meaningful fix was therefore to expose grouped work-mode metadata before touching refinement weights.
- No retrieval weights, summary logic, embedding/index pipeline, or frontend behavior were changed.
- No full benchmark run was executed.
- Targeted validation confirmed:
Data Engineer / Analytics Engineer (w/m/d)now exposes:work_mode = hybridwork_mode_signal_source = evidence_chunk_text
Werkstudent (m/w/d) Python / Data & Energy Analyticsnow exposes:work_mode = remotework_mode_signal_source = multiple
- control sample
Data Analystremains:work_mode = onsite_or_unspecifiedwork_mode_signal_source = none
- The minimal grouped-candidate repair is in place.
- Remote/hybrid clues that already survive into retrieved evidence can now surface as stable grouped metadata for downstream refinement use.
- Weight tuning is still correctly deferred.
- Wire the narrow remote/location refinement path to consume grouped
work_modedirectly and rerun only the targeted remote subset.
- Rerun the narrow remote/location refinement on the 4-query subset after grouped
work_modeexposure, and check whether the signal-exposure repair creates visible gains.
- Executed a subset-only comparison between:
hybridhybrid_plus_remote_location_refinement_v1.2
- Used grouped candidate:
work_modework_mode_signal_source
- Generated:
output/hybrid_plus_remote_location_refinement_v1_2.jsondocs/benchmark_run_record_v1_hybrid_plus_remote_location_refinement_v1_2.mddocs/benchmark_run_summary_v1_remote_location_v1_2_addendum.md
- The previous remote/location branch was blocked by missing grouped-candidate work-mode exposure.
- After fixing that exposure, the project needed one clean rerun to verify whether the repaired signal path is enough to create visible improvement on the intended subset.
- No summary logic, frontend logic, embedding/index pipeline, or broad benchmark scope was changed.
- Only the 4 remote/location queries were rerun.
- Results:
- subset size =
4 - top-1 changes =
0 / 4 - top-1 metadata satisfaction:
hybrid = 3v1.2 = 3
- top-3 metadata satisfaction:
hybrid = 4v1.2 = 4
- source-card drift count =
2 / 4
- subset size =
- Signal-source usage in refined top-3:
none = 4evidence_chunk_text = 3multiple = 3tags = 2
- Grouped work-mode exposure is working and the refinement now consumes real remote/hybrid clues that were previously hidden.
- But the rerun still produced no top-1 gain and no metadata-satisfaction gain on the 4-query subset.
- So the new blocker is no longer signal exposure alone; it is the combination of:
- head score separation
- broad query intent
- and some remote-tagged lower-rank noise
- Run a focused query-quality diagnostic on
remote_data_requirementsbefore considering any new remote/location refinement iteration.
- Define a benchmark-facing query taxonomy that distinguishes broad summary prompts from strict metadata-constraint prompts, so metadata-aware refinement is evaluated on the right query class.
- Added
docs/query_taxonomy_for_refinement_v1.md. - Updated
output/benchmark_query_buckets_v1.jsonwith:query_taxonomytaxonomy_notes
- Classified current metadata-relevant nucleus queries into:
broad_summary_with_metadata_preferencestrict_metadata_constraintmixed_or_ambiguous
- The remote/location diagnostics showed that some benchmark queries mention metadata terms like
remote, but still behave more like broad summary prompts than strict constraint queries. - Without an explicit taxonomy, refinement evaluation can be misread and broad prompts can be used as inappropriate acceptance gates.
- No code or retrieval behavior was changed.
- No new experiment was run.
- Classification was grounded in the existing locked query set and the completed remote/location diagnostics.
remote_data_requirementswas explicitly classified asbroad_summary_with_metadata_preference.
- The benchmark artifacts now better reflect how metadata-aware refinement should be judged.
- Going forward, remote/location refinement should be evaluated mainly on strict or near-strict metadata-constraint prompts, not on broad summary prompts that merely mention remote.
- Use the new taxonomy field in future refinement summaries so acceptance decisions are reported by query type rather than only by bucket.
- Diagnose why
hybrid_plus_remote_location_refinement_v1.1failed to move rankings on the two highest-value remote queries:remote_data_requirementsremote_analytics_expectations
- Inspected the stored
hybridrun record and thehybrid_plus_remote_location_refinement_v1_1.jsoncomparison output for the two target queries. - Compared the top grouped source cards, inferred work-mode labels, visible location signals, and per-card refinement adjustments.
- Classified the blocking pattern without running a new benchmark pass or changing retrieval code.
- The previous v1.1 experiment showed no collateral harm, but also no gain.
- Before considering any further refinement pass, the project needed a cleaner diagnosis of whether the blocker is:
- weak document-side remote metadata
- too-weak weighting
- or both
- No code, retrieval logic, summary logic, or dataset state was changed.
- No new retrieval experiment was executed.
- For both target queries, the inspected top grouped cards carried
onsite_or_unspecifiedwork-mode labels and received the same-0.03adjustment under v1.1. - No top-3 candidate for either query exposed a usable
remoteorhybridsignal, so the refinement could not create a differential lift.
- The dominant blocker is document-side remote/work-mode signal weakness in the actually retrieved head candidates.
- Weighting strength is a secondary factor, but not the primary one, because the current top cards were penalized almost uniformly and there was no nearby remote-tagged alternative to promote.
- The best classification is
C. both, withAclearly dominatingB.
- Inspect whether the corpus contains any strong remote/hybrid candidates for these intents that are failing to enter the retrieved head set at all, before considering any further weight tuning.
- Lock the non-execution baseline artifacts needed for the next benchmark phase: dataset snapshot, query buckets, experiment matrix, and logging contract.
- Created
docs/benchmark_phase_plan_v1.md. - Created
output/benchmark_dataset_snapshot_v1.json. - Created
output/benchmark_query_buckets_v1.json. - Defined the benchmark dataset baseline as:
- the current main SQLite corpus
- plus the accepted 30-sample supplementation layer from the tracking sheet
- Defined a practical benchmark v1 scope:
- 19-query regression nucleus
- 6 staged expansion queries from the existing 25-query eval fixture
- Defined the minimum experiment matrix:
- baseline_current
- dense_only
- lexical_only
- hybrid
- hybrid_plus_metadata_refinement_placeholder
- Wrote down the experiment logging contract for future runs.
- The supplementation freeze is complete, so the next priority is to make the benchmark phase reproducible before running another round of comparisons.
- Locking the baseline artifacts now prevents the first benchmark-phase run from inventing dataset or query definitions ad hoc.
- No retrieval logic was changed.
- No retrieval run or experiment was executed.
- No synthetic results were produced.
- The dataset snapshot and query-bucket files are planning artifacts only.
- Benchmark-phase bootstrap v1 is in place.
- The project now has a locked dataset snapshot, a realistic query-scope definition, a minimum experiment matrix, and a logging contract for the next evaluation cycle.
- Start the first benchmark-phase run record using the locked dataset snapshot and the 19-query nucleus.
- Execute the first real benchmark-phase run records against the locked 19-query nucleus and document the four-mode comparison in a reusable format.
- Executed the benchmark nucleus for:
baseline_currentdense_onlylexical_onlyhybrid
- Wrote machine-readable run outputs to:
output/benchmark_runs_v1/baseline_current.jsonoutput/benchmark_runs_v1/dense_only.jsonoutput/benchmark_runs_v1/lexical_only.jsonoutput/benchmark_runs_v1/hybrid.jsonoutput/benchmark_runs_v1/summary.json
- Created markdown run records:
docs/benchmark_run_record_v1_baseline_current.mddocs/benchmark_run_record_v1_dense_only.mddocs/benchmark_run_record_v1_lexical_only.mddocs/benchmark_run_record_v1_hybrid.md
- Created a cross-mode comparison document:
docs/benchmark_run_summary_v1.md
- The benchmark bootstrap phase was already locked, so the next step was to stop planning and generate the first actual comparison records.
- Writing both JSON outputs and markdown run records makes the benchmark phase easier to repeat and compare without rerunning exploratory notebook-style analysis.
- No retrieval code was modified.
- No supplementation changes were made.
- All four requested modes executed successfully on the locked 19-query nucleus.
- Observed high-level results:
baseline_current:19/19queries returned resultsdense_only:19/19queries returned resultslexical_only:19/19queries returned resultshybrid:19/19queries returned results
- Cross-mode summary from this pass:
dense_onlytop-1 differed frombaseline_currenton7/19querieslexical_onlytop-1 differed frombaseline_currenton19/19querieshybridtop-1 differed frombaseline_currenton0/19queries
- The benchmark phase now has a real first-pass comparison record instead of only planning artifacts.
hybridremains the right family to continue with, whilelexical_onlyis clearly diagnostic-only anddense_onlyremains weaker on metadata-constrained prompts.- The next likely gain is in metadata-aware refinement and precision control rather than another immediate fusion redesign.
- Add the next comparison row on top of the same 19-query nucleus for metadata-aware refinement or another precision-oriented post-retrieval pass.
- Clarify the real implementation difference between
baseline_currentandhybrid, then run a minimal metadata-aware refinement experiment on top ofhybridusing only lightweight, reversible soft adjustments.
- Generated
output/hybrid_plus_metadata_refinement_v1.json. - Created
docs/benchmark_run_record_v1_hybrid_plus_metadata_refinement.md. - Created
docs/benchmark_run_summary_v1_addendum.md. - Ran a new comparison between:
hybridhybrid_plus_metadata_refinement_v1
- Kept the refinement scoped to:
role_familyemployment_typelocation / work_mode
- Explicitly tightened the employment signal so it uses only:
- raw
employment_type - clear
titleclues and does not parse description text for internship-style signals.
- raw
- The earlier benchmark pass showed that
hybridhad no visible top-1 gain overbaseline_current, but the exact reason needed to be tied back to the real code path rather than intuition. - A minimal metadata-aware refinement pass was the next logical experiment because the benchmark summary showed continued weakness on role-boundary and metadata-constrained queries.
- Tightening the employment signal was necessary to avoid semantic pollution from false positives in free-text descriptions.
- No retrieval code, embedding config, index artifacts, frontend, or summary logic were changed.
- The experiment reused:
benchmark_dataset_snapshot_v1- the locked 19-query nucleus
- Observed metrics:
- top-1 changes vs
hybrid:0/19 - source-card drift count:
4/19 - average top-k role mismatch rate:
hybrid = 0.2500refined = 0.2167
- metadata top-1 satisfied count:
hybrid = 4refined = 4
- metadata top-3 satisfied count:
hybrid = 5refined = 5
- top-1 changes vs
- Low-evidence behavior remained unchanged.
baseline_currentandhybridare currently very close in implementation: the active difference is mainly the small merge-stage metadata boost, while the final reranker remainsnoop.hybrid_plus_metadata_refinement_v1produced a mild top-k denoising effect, but no visible top-1 improvement and no metadata-satisfaction improvement on the current nucleus.- Metadata-aware refinement remains promising as a direction, but this v1 should stay experimental rather than being promoted to the mainline path.
- Design a narrower
hybrid_plus_metadata_refinement_v2focused on explicit role-boundary and remote-constraint failures instead of broad weak boosts.
- Test whether a narrower, stronger metadata-aware refinement can create clearer gains on the query types most likely to benefit: explicit role-boundary queries and explicit remote/location constraint queries.
- Generated
output/hybrid_plus_metadata_refinement_v2.json. - Created
docs/benchmark_run_record_v1_hybrid_plus_metadata_refinement_v2.md. - Created
docs/benchmark_run_summary_v1_v2_addendum.md. - Ran
hybrid_plus_metadata_refinement_v2against the locked 19-query nucleus. - Restricted v2 triggers to a 5-query subset only:
remote_data_requirementsanalyst_vs_ml_rolesremote_analytics_expectationsenglish_speaking_jobs_germanyremote_vs_onsite_expectations
- Explicitly did not trigger
visa_sponsorship_requirementsbecause the current corpus lacks a reliable visa-support field.
- The v1 refinement was too broad and too weak, which diluted any possible benefit.
- Narrowing the trigger scope makes it easier to see whether metadata-aware refinement helps where it should help, instead of averaging the effect away across unrelated queries.
- The final v2 pass also corrected two overly broad behaviors discovered during iteration:
- Germany-only queries should not automatically receive work-mode shaping
- role-boundary queries need a small penalty on
unknownrole-family rows to avoid irrelevant unknown-role items rising after adjacent-role demotion
- No retrieval code, embedding/index configuration, frontend logic, or summary pipeline logic was changed.
- The experiment reused:
benchmark_dataset_snapshot_v1- the locked 19-query nucleus
- Final subset metrics:
- subset top-1 changes:
1/5 - subset metadata top-1 satisfied count:
hybrid = 3refined = 4
- subset metadata top-3 satisfied count:
hybrid = 4refined = 4
- subset source-card drift count:
3/5 - subset average top-k role mismatch rate:
hybrid = 0.5000refined = 0.0000
- subset top-1 changes:
- Outside the triggered subset:
- top-1 changes =
0 - source-card drift =
0
- top-1 changes =
- Low-evidence side effects:
- none observed, because the low-evidence query was not triggered
- The narrow-scope v2 experiment produced the clearest positive metadata-refinement signal so far, but only for the remote/location branch.
remote_data_requirementsimproved in a meaningful way, including top-1 metadata satisfaction.- The role-boundary branch is still not trustworthy enough to promote, because its mismatch metric can improve even when lower-ranked results drift to semantically weaker roles.
- Metadata-aware refinement should continue, but only as split branches rather than one broad shared knob.
- Evaluate remote/location refinement and role-boundary refinement as two separate experimental candidates.
- Finalize the supplementation freeze by normalizing all frozen rows in the tracking sheet to
acceptedand confirming the accepted set is ready for benchmark-phase use.
- Updated all frozen analyst and data-engineer rows from
provisional_yestoacceptedinoutput/supplementation_tracking_sheet_v1.csv. - Left backup, hold, and reject rows unchanged.
- Added
docs/supplementation_freeze_review.mdto summarize the final accepted set and its readiness state.
- The supplementation decisions were already complete, but the ledger still mixed frozen rows with provisional status labels.
- Finalizing those labels removes the last administrative blocker before using the accepted set as benchmark-phase augmentation data.
- No retrieval logic, benchmark logic, or application code was changed.
- No retrieval run or new experiment was executed.
- Confirmed the final state counts are:
- accepted = 30
- backup = 1
- hold = 16
- reject = 7
- Confirmed the accepted bucket totals remain unchanged and sum to 30.
- The supplementation freeze is finalized.
- The accepted 30-sample set is now formally synchronized in the tracking sheet and can be used as the benchmark-phase enhancement corpus.
- Start the next benchmark / hybrid evaluation pass using the finalized accepted 30-sample supplementation set.