From adcb9d1d3b6ee7d3c1e34f939bc1e8d40ffed7d2 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 28 Oct 2026 21:09:29 +0400 Subject: [PATCH] =?UTF-8?q?test:=20=D1=81=D0=B2=D0=BE=D0=B4=202=20?= =?UTF-8?q?=E2=80=94=20JS-=D0=BE=D1=80=D0=B0=D0=BA=D1=83=D0=BB=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B8=D1=81=D0=BA=D0=B0,=20=D1=84=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B2=20=D0=B8=20=D0=B2=D0=B0=D1=80=D0=B8?= =?UTF-8?q?=D0=B0=D0=BD=D1=82=D0=BE=D0=B2=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B0=20=C2=AB=D0=B4=D0=BE=C2=BB=20=D0=B8=20=D0=B2=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../fixtures/js_number_text_vectors.json | 86 ++ api/tests/fixtures/svod2_screen_vectors.json | 962 ++++++++++++++++++ perf/analysis/svod2_search_oracle.mjs | 149 +++ perf/analysis/svod2_search_oracle.test.mjs | 60 ++ 4 files changed, 1257 insertions(+) create mode 100644 api/tests/fixtures/js_number_text_vectors.json create mode 100644 api/tests/fixtures/svod2_screen_vectors.json create mode 100644 perf/analysis/svod2_search_oracle.mjs create mode 100644 perf/analysis/svod2_search_oracle.test.mjs diff --git a/api/tests/fixtures/js_number_text_vectors.json b/api/tests/fixtures/js_number_text_vectors.json new file mode 100644 index 0000000..def536a --- /dev/null +++ b/api/tests/fixtures/js_number_text_vectors.json @@ -0,0 +1,86 @@ +[ + { + "input": "0", + "js": "0" + }, + { + "input": "-0", + "js": "0" + }, + { + "input": "1", + "js": "1" + }, + { + "input": "-1", + "js": "-1" + }, + { + "input": "100", + "js": "100" + }, + { + "input": "100.00", + "js": "100" + }, + { + "input": "1234.50", + "js": "1234.5" + }, + { + "input": "12.34", + "js": "12.34" + }, + { + "input": "0.5", + "js": "0.5" + }, + { + "input": "0.30000000000000004", + "js": "0.30000000000000004" + }, + { + "input": "0.000001", + "js": "0.000001" + }, + { + "input": "1e-7", + "js": "1e-7" + }, + { + "input": "-1.5e-10", + "js": "-1.5e-10" + }, + { + "input": "123e-20", + "js": "1.23e-18" + }, + { + "input": "1e15", + "js": "1000000000000000" + }, + { + "input": "1e20", + "js": "100000000000000000000" + }, + { + "input": "1e21", + "js": "1e+21" + }, + { + "input": "123456789012345680000", + "js": "123456789012345680000" + }, + { + "input": "9007199254740993", + "js": "9007199254740992" + }, + { + "input": "5e-324", + "js": "5e-324" + }, + { + "input": "1.7976931348623157e308", + "js": "1.7976931348623157e+308" + } +] diff --git a/api/tests/fixtures/svod2_screen_vectors.json b/api/tests/fixtures/svod2_screen_vectors.json new file mode 100644 index 0000000..5f5987b --- /dev/null +++ b/api/tests/fixtures/svod2_screen_vectors.json @@ -0,0 +1,962 @@ +{ + "rows": [ + { + "data": { + "line_id": 1, + "header": { + "name": "Аренда помещения", + "smeta_type": "КВ", + "smeta_direction": "Развитие", + "org_name": "Филиал Ёлки" + }, + "contract_summary": { + "counterparty": "ООО Ромашка" + }, + "contract_detail": { + "counterparty": null, + "ceiling_amount": 1234.5 + } + } + }, + { + "data": { + "line_id": 2, + "header": { + "name": "АРЕНДА склада", + "smeta_type": "АХР", + "smeta_direction": "Поддержка", + "org_name": "Филиал Ёлки" + }, + "contract_summary": {}, + "contract_detail": { + "counterparty": "ООО Ромашка Плюс", + "ceiling_amount": 100 + } + } + }, + { + "data": { + "line_id": 3, + "header": { + "name": "Ёлка новогодняя", + "smeta_type": "КВ", + "smeta_direction": null, + "org_name": "филиал ёлки " + }, + "contract_summary": { + "counterparty": "ИП Ёж" + }, + "contract_detail": { + "ceiling_amount": 0 + } + } + }, + { + "data": { + "line_id": 4, + "header": { + "name": "ёжик", + "smeta_type": "кв ", + "smeta_direction": "Развитие", + "org_name": "Центр" + }, + "contract_summary": {}, + "contract_detail": { + "ceiling_amount": 1e-7 + } + } + }, + { + "data": { + "line_id": 5, + "header": { + "name": "Услуги связи", + "smeta_type": "КВ", + "smeta_direction": "Поддержка", + "org_name": "Центр" + }, + "contract_summary": {}, + "contract_detail": { + "note": "a b" + } + } + }, + { + "data": { + "line_id": 6, + "header": { + "name": "Без маркера", + "smeta_type": "АХР", + "smeta_direction": "Развитие", + "org_name": "Север" + }, + "contract_summary": {}, + "contract_detail": { + "note": "xy" + } + } + }, + { + "data": { + "line_id": 7, + "header": { + "name": "Флаг", + "smeta_type": "КВ", + "smeta_direction": "Развитие", + "org_name": "Север", + "is_active": true + }, + "contract_summary": { + "flag": true + }, + "contract_detail": { + "note": null + } + } + }, + { + "data": { + "line_id": 8, + "header": { + "name": null, + "smeta_type": null, + "smeta_direction": null, + "org_name": null + }, + "contract_summary": {}, + "contract_detail": {} + } + }, + { + "data": { + "line_id": 9, + "header": { + "name": "Итого 1", + "smeta_type": "АХР", + "smeta_direction": "Поддержка", + "org_name": "Центр" + }, + "contract_summary": { + "counterparty": "ООО Ромашка" + }, + "contract_detail": { + "counterparty": "ООО Ромашка", + "ceiling_amount": 1234.5 + } + } + }, + { + "data": { + "line_id": 10, + "header": { + "name": "Прочее", + "smeta_type": "ПР", + "smeta_direction": "Развитие", + "org_name": "Филиал Ёлки" + }, + "contract_summary": {}, + "contract_detail": { + "ceiling_amount": 1e-7, + "note": "Договор №1" + } + } + }, + { + "data": { + "line_id": 11, + "header": { + "name": "Лицензии", + "smeta_type": "КВ", + "smeta_direction": "Поддержка", + "org_name": "Юг" + }, + "contract_summary": {}, + "contract_detail": { + "ceiling_amount": 100 + } + } + } + ], + "cases": [ + { + "search": "", + "filters": {}, + "line_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ], + "found": { + "found": 11, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 11, + "smeta_type": [ + [ + "АХР", + 3 + ], + [ + "КВ", + 5 + ], + [ + "кв ", + 1 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 4 + ], + [ + "Развитие", + 5 + ] + ], + "org_name": [ + [ + "Север", + 2 + ], + [ + "Филиал Ёлки", + 3 + ], + [ + "филиал ёлки ", + 1 + ], + [ + "Центр", + 3 + ], + [ + "Юг", + 1 + ] + ] + } + }, + { + "search": "аРЕНДА", + "filters": {}, + "line_ids": [ + 1, + 2 + ], + "found": { + "found": 2, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 2, + "smeta_type": [ + [ + "АХР", + 1 + ], + [ + "КВ", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 1 + ], + [ + "Развитие", + 1 + ] + ], + "org_name": [ + [ + "Филиал Ёлки", + 2 + ] + ] + } + }, + { + "search": "ё", + "filters": {}, + "line_ids": [ + 1, + 2, + 3, + 4, + 10 + ], + "found": { + "found": 5, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 5, + "smeta_type": [ + [ + "АХР", + 1 + ], + [ + "КВ", + 2 + ], + [ + "кв ", + 1 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 1 + ], + [ + "Развитие", + 3 + ] + ], + "org_name": [ + [ + "Филиал Ёлки", + 3 + ], + [ + "филиал ёлки ", + 1 + ], + [ + "Центр", + 1 + ] + ] + } + }, + { + "search": "ромаш", + "filters": {}, + "line_ids": [ + 2, + 9 + ], + "found": { + "found": 2, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 2, + "smeta_type": [ + [ + "АХР", + 2 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 2 + ] + ], + "org_name": [ + [ + "Филиал Ёлки", + 1 + ], + [ + "Центр", + 1 + ] + ] + } + }, + { + "search": "1234.5", + "filters": {}, + "line_ids": [ + 1, + 9 + ], + "found": { + "found": 2, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 2, + "smeta_type": [ + [ + "АХР", + 1 + ], + [ + "КВ", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 1 + ], + [ + "Развитие", + 1 + ] + ], + "org_name": [ + [ + "Филиал Ёлки", + 1 + ], + [ + "Центр", + 1 + ] + ] + } + }, + { + "search": "1234.50", + "filters": {}, + "line_ids": [], + "found": { + "found": 0, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 0, + "smeta_type": [], + "smeta_direction": [], + "org_name": [] + } + }, + { + "search": " аренда ", + "filters": {}, + "line_ids": [ + 1, + 2 + ], + "found": { + "found": 2, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 2, + "smeta_type": [ + [ + "АХР", + 1 + ], + [ + "КВ", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 1 + ], + [ + "Развитие", + 1 + ] + ], + "org_name": [ + [ + "Филиал Ёлки", + 2 + ] + ] + } + }, + { + "search": " ", + "filters": {}, + "line_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ], + "found": { + "found": 11, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 11, + "smeta_type": [ + [ + "АХР", + 3 + ], + [ + "КВ", + 5 + ], + [ + "кв ", + 1 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 4 + ], + [ + "Развитие", + 5 + ] + ], + "org_name": [ + [ + "Север", + 2 + ], + [ + "Филиал Ёлки", + 3 + ], + [ + "филиал ёлки ", + 1 + ], + [ + "Центр", + 3 + ], + [ + "Юг", + 1 + ] + ] + } + }, + { + "search": "1", + "filters": {}, + "line_ids": [ + 1, + 2, + 4, + 9, + 10, + 11 + ], + "found": { + "found": 6, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 6, + "smeta_type": [ + [ + "АХР", + 2 + ], + [ + "КВ", + 2 + ], + [ + "кв ", + 1 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 3 + ], + [ + "Развитие", + 3 + ] + ], + "org_name": [ + [ + "Филиал Ёлки", + 3 + ], + [ + "Центр", + 2 + ], + [ + "Юг", + 1 + ] + ] + } + }, + { + "search": "", + "filters": { + "smeta_type": "кв " + }, + "line_ids": [ + 1, + 3, + 4, + 5, + 7, + 11 + ], + "found": { + "found": 11, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 11, + "smeta_type": [ + [ + "АХР", + 3 + ], + [ + "КВ", + 5 + ], + [ + "кв ", + 1 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 2 + ], + [ + "Развитие", + 3 + ] + ], + "org_name": [ + [ + "Север", + 1 + ], + [ + "Филиал Ёлки", + 1 + ], + [ + "филиал ёлки ", + 1 + ], + [ + "Центр", + 2 + ], + [ + "Юг", + 1 + ] + ] + } + }, + { + "search": "", + "filters": { + "org_name": "Филиал Ёлки" + }, + "line_ids": [ + 1, + 2, + 3, + 10 + ], + "found": { + "found": 11, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 11, + "smeta_type": [ + [ + "АХР", + 1 + ], + [ + "КВ", + 2 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 1 + ], + [ + "Развитие", + 2 + ] + ], + "org_name": [ + [ + "Север", + 2 + ], + [ + "Филиал Ёлки", + 3 + ], + [ + "филиал ёлки ", + 1 + ], + [ + "Центр", + 3 + ], + [ + "Юг", + 1 + ] + ] + } + }, + { + "search": "", + "filters": { + "smeta_type": "КВ", + "smeta_direction": "Развитие" + }, + "line_ids": [ + 1, + 4, + 7 + ], + "found": { + "found": 11, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 11, + "smeta_type": [ + [ + "АХР", + 1 + ], + [ + "КВ", + 2 + ], + [ + "кв ", + 1 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 2 + ], + [ + "Развитие", + 3 + ] + ], + "org_name": [ + [ + "Север", + 1 + ], + [ + "Филиал Ёлки", + 1 + ], + [ + "Центр", + 1 + ] + ] + } + }, + { + "search": "ё", + "filters": { + "smeta_type": "КВ" + }, + "line_ids": [ + 1, + 3, + 4 + ], + "found": { + "found": 5, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 5, + "smeta_type": [ + [ + "АХР", + 1 + ], + [ + "КВ", + 2 + ], + [ + "кв ", + 1 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Развитие", + 2 + ] + ], + "org_name": [ + [ + "Филиал Ёлки", + 1 + ], + [ + "филиал ёлки ", + 1 + ], + [ + "Центр", + 1 + ] + ] + } + }, + { + "search": "", + "filters": { + "org_name": "филиал ёлки" + }, + "line_ids": [ + 1, + 2, + 3, + 10 + ], + "found": { + "found": 11, + "total": 11 + }, + "facets": { + "total": 11, + "search_total": 11, + "smeta_type": [ + [ + "АХР", + 1 + ], + [ + "КВ", + 2 + ], + [ + "ПР", + 1 + ] + ], + "smeta_direction": [ + [ + "Поддержка", + 1 + ], + [ + "Развитие", + 2 + ] + ], + "org_name": [ + [ + "Север", + 2 + ], + [ + "Филиал Ёлки", + 3 + ], + [ + "филиал ёлки ", + 1 + ], + [ + "Центр", + 3 + ], + [ + "Юг", + 1 + ] + ] + } + } + ] +} diff --git a/perf/analysis/svod2_search_oracle.mjs b/perf/analysis/svod2_search_oracle.mjs new file mode 100644 index 0000000..2343a53 --- /dev/null +++ b/perf/analysis/svod2_search_oracle.mjs @@ -0,0 +1,149 @@ +// Оракул экрана «до» свода 2 (Svod2Page на коммите 399234a + material-react-table). Копии дословные. +// matchesSearch: Svod2Page.jsx:283-294; equals: material-react-table/dist/index.esm.js:663-667. +import { mkdirSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const matchesSearch = (row, query) => { + const searchableData = { + ...row.data?.header, + ...row.data?.contract_summary, + ...row.data?.contract_detail, + }; + return Object.values(searchableData).some((value) => + String(value ?? '') + .toLowerCase() + .includes(query), + ); +}; + +const equals = (row, id, filterValue) => { + var _a; + return ((_a = row.getValue(id)) === null || _a === void 0 ? void 0 : _a.toString().toLowerCase().trim()) === + filterValue.toString().toLowerCase().trim(); +}; +equals.autoRemove = (val) => !val; + +export { matchesSearch, equals }; + +export const FILTER_COLUMNS = ['smeta_type', 'smeta_direction', 'org_name']; + +// accessorFn колонок: valueAt(row, ['header', id]) +const asTableRow = (row, id) => ({ getValue: () => row.data?.header?.[id] }); +const cellValue = (row, id) => row.data?.header?.[id]; + +const activeFilters = (filters, skipId) => + FILTER_COLUMNS.filter((id) => id !== skipId && filters[id] && !equals.autoRemove(filters[id])); + +const bySearch = (rows, search) => { + const normalizedSearch = String(search ?? '').trim().toLowerCase(); + return normalizedSearch ? rows.filter((row) => matchesSearch(row, normalizedSearch)) : rows; +}; + +const byFilters = (rows, filters, skipId) => { + const ids = activeFilters(filters, skipId); + return rows.filter((row) => ids.every((id) => equals({ getValue: () => cellValue(row, id) }, id, filters[id]))); +}; + +export function screenRows(rows, { search = '', filters = {} } = {}) { + return byFilters(bySearch(rows, search), filters, null); +} + +export function screenFound(rows, search) { + return { found: bySearch(rows, search).length, total: rows.length }; +} + +export function screenFacets(rows, { search = '', filters = {} } = {}) { + const searched = bySearch(rows, search); + const result = { total: rows.length, search_total: searched.length }; + for (const id of FILTER_COLUMNS) { + const counts = new Map(); + for (const row of byFilters(searched, filters, id)) { + const value = asTableRow(row, id).getValue(id); + counts.set(value, (counts.get(value) ?? 0) + 1); + } + result[id] = Array.from(counts.keys()) + .filter((value) => value !== null && value !== undefined) + .sort((a, b) => a.localeCompare(b)) + .map((value) => [value, counts.get(value)]); + } + return result; +} + +export const NUMBER_VECTOR_INPUTS = [ + '0', '-0', '1', '-1', '100', '100.00', '1234.50', '12.34', '0.5', '0.30000000000000004', + '0.000001', '1e-7', '-1.5e-10', '123e-20', '1e15', '1e20', '1e21', '123456789012345680000', + '9007199254740993', '5e-324', '1.7976931348623157e308', +]; + +// Числа в строках берём через JSON.parse, чтобы запись была как у JSON-ответа API. +const J = (text) => JSON.parse(text); + +export const SYNTHETIC_ROWS = [ + { data: { line_id: 1, header: { name: 'Аренда помещения', smeta_type: 'КВ', smeta_direction: 'Развитие', org_name: 'Филиал Ёлки' }, + contract_summary: { counterparty: 'ООО Ромашка' }, contract_detail: { counterparty: null, ceiling_amount: J('1234.50') } } }, + { data: { line_id: 2, header: { name: 'АРЕНДА склада', smeta_type: 'АХР', smeta_direction: 'Поддержка', org_name: 'Филиал Ёлки' }, + contract_summary: {}, contract_detail: { counterparty: 'ООО Ромашка Плюс', ceiling_amount: J('100') } } }, + { data: { line_id: 3, header: { name: 'Ёлка новогодняя', smeta_type: 'КВ', smeta_direction: null, org_name: 'филиал ёлки ' }, + contract_summary: { counterparty: 'ИП Ёж' }, contract_detail: { ceiling_amount: J('0') } } }, + { data: { line_id: 4, header: { name: 'ёжик', smeta_type: 'кв ', smeta_direction: 'Развитие', org_name: 'Центр' }, + contract_summary: {}, contract_detail: { ceiling_amount: J('1e-7') } } }, + { data: { line_id: 5, header: { name: 'Услуги связи', smeta_type: 'КВ', smeta_direction: 'Поддержка', org_name: 'Центр' }, + contract_summary: {}, contract_detail: { note: 'a b' } } }, + { data: { line_id: 6, header: { name: 'Без маркера', smeta_type: 'АХР', smeta_direction: 'Развитие', org_name: 'Север' }, + contract_summary: {}, contract_detail: { note: 'xy' } } }, + { data: { line_id: 7, header: { name: 'Флаг', smeta_type: 'КВ', smeta_direction: 'Развитие', org_name: 'Север', is_active: true }, + contract_summary: { flag: true }, contract_detail: { note: null } } }, + { data: { line_id: 8, header: { name: null, smeta_type: null, smeta_direction: null, org_name: null }, + contract_summary: {}, contract_detail: {} } }, + { data: { line_id: 9, header: { name: 'Итого 1', smeta_type: 'АХР', smeta_direction: 'Поддержка', org_name: 'Центр' }, + contract_summary: { counterparty: 'ООО Ромашка' }, contract_detail: { counterparty: 'ООО Ромашка', ceiling_amount: J('1234.5') } } }, + { data: { line_id: 10, header: { name: 'Прочее', smeta_type: 'ПР', smeta_direction: 'Развитие', org_name: 'Филиал Ёлки' }, + contract_summary: {}, contract_detail: { ceiling_amount: J('1e-7'), note: 'Договор №1' } } }, + { data: { line_id: 11, header: { name: 'Лицензии', smeta_type: 'КВ', smeta_direction: 'Поддержка', org_name: 'Юг' }, + contract_summary: {}, contract_detail: { ceiling_amount: J('100.00') } } }, +]; + +export const SCREEN_CASES = [ + { search: '', filters: {} }, + { search: 'аРЕНДА', filters: {} }, + { search: 'ё', filters: {} }, + { search: 'ромаш', filters: {} }, + { search: '1234.5', filters: {} }, + { search: '1234.50', filters: {} }, + { search: ' аренда ', filters: {} }, + { search: ' ', filters: {} }, + { search: '1', filters: {} }, + { search: '', filters: { smeta_type: 'кв ' } }, + { search: '', filters: { org_name: 'Филиал Ёлки' } }, + { search: '', filters: { smeta_type: 'КВ', smeta_direction: 'Развитие' } }, + { search: 'ё', filters: { smeta_type: 'КВ' } }, + { search: '', filters: { org_name: 'филиал ёлки' } }, +]; + +export function emitNumberVectors() { + return NUMBER_VECTOR_INPUTS.map((input) => ({ input, js: String(JSON.parse(input)) })); +} + +export function emitScreenVectors() { + return { + rows: SYNTHETIC_ROWS, + cases: SCREEN_CASES.map(({ search, filters }) => ({ + search, + filters, + line_ids: screenRows(SYNTHETIC_ROWS, { search, filters }).map((r) => r.data.line_id), + found: screenFound(SYNTHETIC_ROWS, search), + facets: screenFacets(SYNTHETIC_ROWS, { search, filters }), + })), + }; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const i = process.argv.indexOf('--emit-vectors'); + if (i > 0 && process.argv[i + 1]) { + const dir = process.argv[i + 1]; + mkdirSync(dir, { recursive: true }); + writeFileSync(path.join(dir, 'js_number_text_vectors.json'), `${JSON.stringify(emitNumberVectors(), null, 2)}\n`); + writeFileSync(path.join(dir, 'svod2_screen_vectors.json'), `${JSON.stringify(emitScreenVectors(), null, 2)}\n`); + } +} diff --git a/perf/analysis/svod2_search_oracle.test.mjs b/perf/analysis/svod2_search_oracle.test.mjs new file mode 100644 index 0000000..749a8a5 --- /dev/null +++ b/perf/analysis/svod2_search_oracle.test.mjs @@ -0,0 +1,60 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { + emitNumberVectors, + emitScreenVectors, + equals, + matchesSearch, + screenFacets, + screenFound, + screenRows, +} from './svod2_search_oracle.mjs'; + +const mk = (id, type, dir, org) => ({ data: { line_id: id, header: { smeta_type: type, smeta_direction: dir, org_name: org } } }); +const rows2 = [mk(1, 'КВ', 'Развитие', 'А'), mk(2, 'АХР', 'Поддержка', 'Б')]; +const rows3 = [mk(1, 'КВ', 'Р', 'Филиал Ёлки'), mk(2, 'КВ', 'Р', 'Филиал Ёлки'), mk(3, 'КВ', 'Р', null)]; + +test('перекрытое поле contract_summary не ищется', () => { + const row = { data: { header: { name: 'x' }, contract_summary: { counterparty: 'ООО Ромашка' }, contract_detail: { counterparty: null } } }; + assert.equal(matchesSearch(row, 'ромаш'), false); +}); +test('число ищется в записи JS', () => { + const row = { data: { header: {}, contract_summary: {}, contract_detail: JSON.parse('{"ceiling_amount": 1234.50}') } }; + assert.equal(matchesSearch(row, '1234.5'), true); + assert.equal(matchesSearch(row, '1234.50'), false); +}); +test('screenRows: trim + lower, NBSP по краям', () => { + const rows = [{ data: { line_id: 1, header: { name: 'Аренда помещения' } } }]; + assert.deepEqual(screenRows(rows, { search: ' АРЕНДА ' }).map((r) => r.data.line_id), [1]); + assert.deepEqual(screenRows(rows, { search: ' аренда ' }).map((r) => r.data.line_id), [1]); +}); +test('equals: регистр и пробелы по краям, null не проходит', () => { + const r = (v) => ({ getValue: () => v }); + assert.equal(equals(r('КВ'), 'smeta_type', 'кв '), true); + assert.equal(equals(r(null), 'smeta_type', 'кв'), false); +}); +test('screenFacets: свой фильтр не применяется, чужие — да', () => { + const f = screenFacets(rows2, { filters: { smeta_type: 'КВ' } }); + assert.deepEqual(f.smeta_type, [['АХР', 1], ['КВ', 1]]); + assert.deepEqual(f.smeta_direction, [['Развитие', 1]]); +}); +test('screenFacets: одинаковые названия — один вариант, null не выдаётся', () => { + assert.deepEqual(screenFacets(rows3).org_name, [['Филиал Ёлки', 2]]); +}); +test('screenFound считает только поиск', () => { + assert.deepEqual(screenFound(rows2, ''), { found: 2, total: 2 }); +}); +test('векторы чисел', () => { + const v = Object.fromEntries(emitNumberVectors().map(({ input, js }) => [input, js])); + assert.equal(v['1e21'], '1e+21'); + assert.equal(v['-0'], '0'); + assert.equal(v['1e-7'], '1e-7'); + assert.equal(v['0.000001'], '0.000001'); + assert.equal(v['100.00'], '100'); + assert.equal(v['9007199254740993'], '9007199254740992'); +}); +test('векторы экрана: не меньше 12 случаев', () => { + const s = emitScreenVectors(); + assert.ok(s.cases.length >= 12); + assert.ok(s.rows.length >= 10 && s.rows.length <= 15); +});