| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- // Copyright (c) 未来飞马
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this
- // file, You can obtain one at https://mozilla.org/MPL/2.0/.
- //
- // Trademark Notice:
- // The MPL-2.0 license grants copyright permissions for source code only.
- // It does NOT grant any rights to use trademarks including "未来飞马",
- // "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
- // Any use of these trademarks requires separate written permission.
- /**
- * 测试夹具生成器:现场合成 .docx / .pptx / .png,落盘到指定目录。
- *
- * 刻意不在仓库里存任何二进制素材(.gitignore 也排除了图片)——夹具全部即时生成,
- * 内容为合成的假数据,不含任何真实 PII。
- */
- import fs from 'node:fs';
- import path from 'node:path';
- import { spawnSync } from 'node:child_process';
- import { ensureDir, resolvePython } from '../lib.mjs';
- import { makeTinyPng } from '../case-intake.mjs';
- const PY_BUILD = `
- import sys, zipfile, os
- target = sys.argv[1]
- doc_xml = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
- xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
- xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
- xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><w:body>
- <w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>IC 计算机科学 考前冲刺案例</w:t></w:r></w:p>
- <w:p><w:r><w:t>客户在读帝国理工本科二年级,期末前两周找到我们,担心挂科。</w:t></w:r></w:p>
- <w:p><w:r><w:t>跟进状态:异议处理中。客户觉得贵,家长不放心老师。</w:t></w:r></w:p>
- <w:p><w:r><w:t>结果:期末提分 18 分,家长很满意,已续课。</w:t></w:r></w:p>
- <w:p><w:r><w:drawing><wp:inline><a:graphic><a:blip r:embed="rId5"/></a:graphic></wp:inline></w:drawing></w:r></w:p>
- <w:p><w:hyperlink r:id="rId9"><w:r><w:t>查看成绩单</w:t></w:r></w:hyperlink></w:p>
- </w:body></w:document>'''
- doc_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
- <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/>
- <Relationship Id="rId9" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="https://example.invalid/score" TargetMode="External"/>
- </Relationships>'''
- pres_xml = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
- xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
- <p:sldIdLst><p:sldId id="256" r:id="rId1"/><p:sldId id="257" r:id="rId2"/></p:sldIdLst></p:presentation>'''
- pres_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
- <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
- <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide2.xml"/>
- </Relationships>'''
- def slide(title, body, with_pic, rid):
- pic = ('<p:pic><p:blipFill><a:blip r:embed="%s"/></p:blipFill></p:pic>' % rid) if with_pic else ''
- return '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <p:sld xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
- xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
- xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><p:cSld><p:spTree>
- <p:sp><p:nvSpPr><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr><p:txBody><a:p><a:r><a:t>%s</a:t></a:r></a:p></p:txBody></p:sp>
- <p:sp><p:nvSpPr/><p:txBody><a:p><a:r><a:t>%s</a:t></a:r></a:p></p:txBody></p:sp>
- %s
- </p:spTree></p:cSld></p:sld>''' % (title, body, pic)
- slide_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
- <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/>
- </Relationships>'''
- png = bytes.fromhex(
- "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
- "0000000a49444154789c6300010000050001"
- "0d0a2db40000000049454e44ae426082")
- with zipfile.ZipFile(os.path.join(target, 'case.docx'), 'w') as zf:
- zf.writestr('[Content_Types].xml', '<Types/>')
- zf.writestr('word/document.xml', doc_xml)
- zf.writestr('word/_rels/document.xml.rels', doc_rels)
- zf.writestr('word/media/image1.png', png)
- with zipfile.ZipFile(os.path.join(target, 'deck.pptx'), 'w') as zf:
- zf.writestr('[Content_Types].xml', '<Types/>')
- zf.writestr('ppt/presentation.xml', pres_xml)
- zf.writestr('ppt/_rels/presentation.xml.rels', pres_rels)
- zf.writestr('ppt/slides/slide1.xml', slide('案例合集', '英国本科 方案推荐中 导师匹配', True, 'rId2'))
- zf.writestr('ppt/slides/slide2.xml', slide('结果与反馈', '期末提分 出分反馈 家长认可', False, 'rId2'))
- zf.writestr('ppt/slides/_rels/slide1.xml.rels', slide_rels)
- zf.writestr('ppt/media/image1.png', png)
- print('ok')
- `;
- /**
- * 生成夹具。
- * @param {string} dir 目标目录
- * @returns {{docx:string, pptx:string, images:string[]}}
- */
- export function makeFixtures(dir) {
- const target = ensureDir(dir);
- const python = resolvePython();
- const run = spawnSync(python, ['-c', PY_BUILD, target], { encoding: 'utf8' });
- if (run.status !== 0) {
- throw new Error(`夹具生成失败:${run.stderr || run.stdout}`);
- }
- const images = [];
- // 九宫格:9 张同尺寸、同一批连发(时间戳相邻)→ 应判为同一组 3x3
- const base = 1_700_000_000_000;
- for (let i = 0; i < 9; i++) {
- const file = path.join(target, `grid-${String(i + 1).padStart(2, '0')}.png`);
- fs.writeFileSync(file, makeTinyPng(400, 400));
- fs.utimesSync(file, new Date(base + i * 1000), new Date(base + i * 1000));
- images.push(file);
- }
- // 另一批:不同尺寸 + 不同时间 → 不应并进九宫格
- const other = path.join(target, 'other-01.png');
- fs.writeFileSync(other, makeTinyPng(1600, 900));
- fs.utimesSync(other, new Date(base - 3 * 86_400_000), new Date(base - 3 * 86_400_000));
- images.push(other);
- return { docx: path.join(target, 'case.docx'), pptx: path.join(target, 'deck.pptx'), images };
- }
|