Track the source-of-truth code, skills, bootstrap rules, docs, and packaging files before adding new features. Exclude local vault config, generated skill mirrors, temporary exports, virtual environments, and business data files from version control. Validation: inspected staged file list and git ignored status before commit.
429 lines
21 KiB
Python
429 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
from __future__ import annotations
|
||
|
||
import csv
|
||
import hashlib
|
||
import json
|
||
import os
|
||
import re
|
||
import zipfile
|
||
from collections import Counter, defaultdict
|
||
from datetime import datetime, timezone
|
||
from pathlib import Path
|
||
from xml.etree import ElementTree as ET
|
||
|
||
ROOT = Path('/home/nac/smb/品质部/品质管理资料夹2009.11.3/PPAP资料')
|
||
VAULT = Path('/home/nac/repo/obsidian-wiki-linux/kb/bjd-vault')
|
||
META = VAULT / '_meta'
|
||
OUT = VAULT / 'projects/bjd/references'
|
||
RUN_DATE = '20260611'
|
||
|
||
CATEGORY_PATTERNS = [
|
||
('ppap_submission_list', r'PPAP提交清单|PPAP文件清单|文件清单'),
|
||
('psw_part_submission_warrant', r'零件提交保证书|PSW|Part Submission Warrant'),
|
||
('dimensional_report', r'全尺寸|寸法|尺寸|样品检验|尺寸测量|全尺寸报告'),
|
||
('msa_grr', r'MSA|GR&R|GRR|量测系统|测量系统|计数型'),
|
||
('process_capability', r'CPK|PPK|CMK|过程能力|设备能力|X-R|X-R图'),
|
||
('reliability_test', r'可靠性|测试报告|测试成绩|低温|高温|耐湿|盐雾|沾锡|可焊|耐焊热|保持力|接触电阻|绝缘耐压|重复插拔|拉拔|DV|PV'),
|
||
('drawing', r'图纸|产品图|包装图|Model|平面图|胶芯|端子|后盖|垫片|组立品|完成品'),
|
||
('material_compliance', r'SGS|RoHS|Halogen|卤|材料|C5210|C5191|C2680|LCP|PA9T|GN2330|MG350'),
|
||
('control_plan', r'控制计划|Control Plan|产控制计划'),
|
||
('pfmea_dfmea', r'PFMEA|DFMEA|失效模式'),
|
||
('process_flow', r'流程图|工艺流程|过程流程'),
|
||
('apqp_plan_review', r'APQP|先期策划|工作计划|阶段评审|可行性|项目小组|立项|设计输入|设计输出|设计开发|试产总结|质量策划'),
|
||
('specification', r'规格书|产品质量标准|标准样品|特殊特性'),
|
||
('equipment_training', r'设备|量具|试验装置|培训|作业人员|技能'),
|
||
('lab_manual', r'实验室手册'),
|
||
]
|
||
CATEGORY_LABELS = {
|
||
'ppap_submission_list': 'PPAP提交清单/文件清单',
|
||
'psw_part_submission_warrant': '零件提交保证书/PSW',
|
||
'dimensional_report': '全尺寸/样品寸法报告',
|
||
'msa_grr': 'MSA/GR&R/测量系统',
|
||
'process_capability': 'CPK/PPK/CMK/过程能力',
|
||
'reliability_test': '可靠性/DV/PV/功能测试',
|
||
'drawing': '图纸/结构件图',
|
||
'material_compliance': '材料/SGS/RoHS/有卤无卤',
|
||
'control_plan': '控制计划',
|
||
'pfmea_dfmea': 'PFMEA/DFMEA',
|
||
'process_flow': '过程流程/工艺流程',
|
||
'apqp_plan_review': 'APQP计划/评审/可行性',
|
||
'specification': '规格书/标准样品/特殊特性',
|
||
'equipment_training': '设备/量具/培训',
|
||
'lab_manual': '实验室手册',
|
||
'other': '其他',
|
||
}
|
||
|
||
CUSTOMER_PATTERNS = [
|
||
('Valeo/法雷奥', r'法雷奥|Valeo'),
|
||
('Jicheng/集诚', r'集诚|集成'),
|
||
('Ford', r'18B966|Ford'),
|
||
('Ineos', r'Ineos'),
|
||
]
|
||
|
||
FAMILY_PATTERNS = [
|
||
('FPC 0.3-3', r'0\.3-3'),
|
||
('FPC 0.5系列', r'0\.5-\d+'),
|
||
('FPC 0.8系列', r'0\.8-\d+'),
|
||
('FPC 1.0系列', r'1\.0-\d+|1\.0PH'),
|
||
('FPC 1.25-2', r'1\.25-2'),
|
||
('FFC/FPC排线', r'FFC|FPC'),
|
||
('BTB 0.5-5', r'BTB0\.5-5|BTB'),
|
||
('FAKRA', r'FAKRA'),
|
||
('WTB/Wafer 2.0', r'WAFER|Wafer|2\.0-10|2\.0-12'),
|
||
('WTB 28+4P', r'28\+4'),
|
||
('LCD 2.0-2', r'LCD ?2\.0-2'),
|
||
('ISO-C P5.0', r'ISO-C|P5\.0'),
|
||
('Pin Header/弯插', r'P1\.8|P2\.2|P2\.2-3|P3\.5|P5\.0|10PIN|40P弯插|36P弯插'),
|
||
('P13端子', r'P13'),
|
||
]
|
||
|
||
|
||
def now_iso():
|
||
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z')
|
||
|
||
|
||
def sha1_path(path: Path) -> str:
|
||
return hashlib.sha1(str(path).encode('utf-8', 'ignore')).hexdigest()[:12]
|
||
|
||
|
||
def clean(s: str) -> str:
|
||
return re.sub(r'\s+', ' ', s.replace('\x00', ' ')).strip()
|
||
|
||
|
||
def infer_category(text: str) -> str:
|
||
for cat, pat in CATEGORY_PATTERNS:
|
||
if re.search(pat, text, re.I):
|
||
return cat
|
||
return 'other'
|
||
|
||
|
||
def infer_all_categories(text: str) -> list[str]:
|
||
cats = []
|
||
for cat, pat in CATEGORY_PATTERNS:
|
||
if re.search(pat, text, re.I):
|
||
cats.append(cat)
|
||
return cats or ['other']
|
||
|
||
|
||
def infer_customer(text: str) -> list[str]:
|
||
out=[]
|
||
for label, pat in CUSTOMER_PATTERNS:
|
||
if re.search(pat, text, re.I):
|
||
out.append(label)
|
||
return out
|
||
|
||
|
||
def infer_family(text: str) -> list[str]:
|
||
out=[]
|
||
for label, pat in FAMILY_PATTERNS:
|
||
if re.search(pat, text, re.I):
|
||
out.append(label)
|
||
# more exact series tokens, useful for lookup
|
||
for token in sorted(set(re.findall(r'(?<!\d)(?:0\.3|0\.5|0\.8|1\.0|1\.25|1\.8|2\.0|2\.2|3\.5|5\.0)-\d+(?:B)?', text, re.I))):
|
||
out.append(token)
|
||
return list(dict.fromkeys(out))
|
||
|
||
|
||
def xlsx_text_sample(path: Path, max_chars=5000) -> str:
|
||
try:
|
||
with zipfile.ZipFile(path) as z:
|
||
names = set(z.namelist())
|
||
shared=[]
|
||
if 'xl/sharedStrings.xml' in names:
|
||
root = ET.fromstring(z.read('xl/sharedStrings.xml'))
|
||
ns = {'a':'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
|
||
for si in root.findall('.//a:si', ns):
|
||
parts=[t.text or '' for t in si.findall('.//a:t', ns)]
|
||
if parts:
|
||
shared.append(''.join(parts))
|
||
cells=[]
|
||
for name in sorted(n for n in names if n.startswith('xl/worksheets/sheet') and n.endswith('.xml'))[:3]:
|
||
root=ET.fromstring(z.read(name))
|
||
ns={'a':'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
|
||
for c in root.findall('.//a:c', ns)[:300]:
|
||
v=c.find('a:v', ns)
|
||
if v is None or v.text is None:
|
||
continue
|
||
val=v.text
|
||
if c.get('t') == 's':
|
||
try:
|
||
val=shared[int(val)]
|
||
except Exception:
|
||
pass
|
||
cells.append(val)
|
||
return clean(' '.join(cells))[:max_chars]
|
||
except Exception:
|
||
return ''
|
||
|
||
|
||
def binary_text_sample(path: Path, max_chars=2000) -> str:
|
||
# Small, dependency-free fallback for old xls/doc/pdf names/text fragments. It is not a full parser.
|
||
try:
|
||
data = path.read_bytes()[:2_000_000]
|
||
except Exception:
|
||
return ''
|
||
words=[]
|
||
cur=[]
|
||
for b in data:
|
||
if 32 <= b <= 126:
|
||
cur.append(chr(b))
|
||
else:
|
||
if len(cur) >= 4:
|
||
words.append(''.join(cur))
|
||
cur=[]
|
||
if len(' '.join(words)) > max_chars:
|
||
break
|
||
if len(cur) >= 4:
|
||
words.append(''.join(cur))
|
||
return clean(' '.join(words))[:max_chars]
|
||
|
||
|
||
def collect():
|
||
records=[]
|
||
for path in ROOT.rglob('*'):
|
||
if not path.is_file():
|
||
continue
|
||
try:
|
||
st=path.stat()
|
||
except OSError:
|
||
continue
|
||
rel=path.relative_to(ROOT).as_posix()
|
||
top=rel.split('/')[0]
|
||
ext=path.suffix.lower().lstrip('.') or 'no_ext'
|
||
text_context=f'{rel} {path.name}'
|
||
sample=''
|
||
if ext == 'xlsx':
|
||
sample=xlsx_text_sample(path)
|
||
elif ext in {'txt','log'}:
|
||
try: sample=path.read_text(encoding='utf-8', errors='ignore')[:5000]
|
||
except Exception: sample=''
|
||
elif ext in {'xls','doc','pdf'}:
|
||
# Only use binary strings for classification hints; full extraction requires external tools/OCR.
|
||
sample=binary_text_sample(path, max_chars=1200)
|
||
all_text=f'{text_context} {sample}'
|
||
cats=infer_all_categories(all_text)
|
||
records.append({
|
||
'id': sha1_path(path),
|
||
'path': str(path),
|
||
'relative_path': rel,
|
||
'top_package': top,
|
||
'filename': path.name,
|
||
'extension': ext,
|
||
'size_bytes': st.st_size,
|
||
'modified_at': datetime.fromtimestamp(st.st_mtime, timezone.utc).replace(microsecond=0).isoformat().replace('+00:00','Z'),
|
||
'categories': cats,
|
||
'primary_category': cats[0],
|
||
'families': infer_family(all_text),
|
||
'customers': infer_customer(all_text),
|
||
'text_sample': clean(sample)[:1000],
|
||
})
|
||
return records
|
||
|
||
|
||
def write_jsonl(path, rows):
|
||
with path.open('w', encoding='utf-8') as f:
|
||
for r in rows:
|
||
f.write(json.dumps(r, ensure_ascii=False) + '\n')
|
||
|
||
|
||
def write_csv(path, rows):
|
||
fields=['id','relative_path','top_package','filename','extension','size_bytes','modified_at','primary_category','categories','families','customers','path']
|
||
with path.open('w', encoding='utf-8-sig', newline='') as f:
|
||
w=csv.DictWriter(f, fieldnames=fields)
|
||
w.writeheader()
|
||
for r in rows:
|
||
row={k:r.get(k,'') for k in fields}
|
||
for k in ['categories','families','customers']:
|
||
row[k]='; '.join(row[k] or [])
|
||
w.writerow(row)
|
||
|
||
|
||
def package_summary(records):
|
||
by=defaultdict(list)
|
||
for r in records:
|
||
by[r['top_package']].append(r)
|
||
rows=[]
|
||
for pkg, rs in by.items():
|
||
cats=Counter(c for r in rs for c in r['categories'])
|
||
exts=Counter(r['extension'] for r in rs)
|
||
fam=Counter(f for r in rs for f in r['families'])
|
||
cust=Counter(c for r in rs for c in r['customers'])
|
||
notable=[]
|
||
wanted=['ppap_submission_list','psw_part_submission_warrant','dimensional_report','msa_grr','process_capability','reliability_test','pfmea_dfmea','control_plan','drawing','material_compliance']
|
||
for cat in wanted:
|
||
hit=next((r for r in rs if cat in r['categories']), None)
|
||
if hit:
|
||
notable.append(f"{CATEGORY_LABELS[cat]}: {hit['filename']}")
|
||
rows.append({
|
||
'package': pkg,
|
||
'file_count': len(rs),
|
||
'extensions': dict(exts.most_common()),
|
||
'categories': dict(cats.most_common()),
|
||
'families': [x for x,_ in fam.most_common(8)],
|
||
'customers': [x for x,_ in cust.most_common(5)],
|
||
'notable_files': notable[:12],
|
||
})
|
||
rows.sort(key=lambda x: (-x['file_count'], x['package']))
|
||
return rows
|
||
|
||
|
||
def esc(s):
|
||
return str(s).replace('|','\\|').replace('\n',' ')
|
||
|
||
|
||
def table_packages(rows, limit=80):
|
||
lines=['| 资料包 | 文件数 | 识别产品/客户 | 覆盖资料类型 | 代表文件 |','|---|---:|---|---|---|']
|
||
for r in rows[:limit]:
|
||
cats=';'.join(CATEGORY_LABELS.get(k,k) for k in list(r['categories'].keys())[:8])
|
||
fc=';'.join((r['families']+r['customers'])[:8]) or '未识别'
|
||
nf=';'.join(r['notable_files'][:4])
|
||
lines.append(f"| {esc(r['package'])} | {r['file_count']} | {esc(fc)} | {esc(cats)} | {esc(nf)} |")
|
||
return '\n'.join(lines)
|
||
|
||
|
||
def category_table(counter):
|
||
lines=['| 类型 | 文件数 |','|---|---:|']
|
||
for k,v in counter.most_common():
|
||
lines.append(f"| {CATEGORY_LABELS.get(k,k)} | {v} |")
|
||
return '\n'.join(lines)
|
||
|
||
|
||
def write_pages(records, packages, jsonl, csvp, package_json):
|
||
ts=now_iso()
|
||
total=len(records)
|
||
ext=Counter(r['extension'] for r in records)
|
||
cats=Counter(c for r in records for c in r['categories'])
|
||
fam=Counter(f for r in records for f in r['families'])
|
||
cust=Counter(c for r in records for c in r['customers'])
|
||
top_packages=packages[:20]
|
||
|
||
index_page=OUT / f'bjd-ppap-apqp-archive-index-{RUN_DATE}.md'
|
||
index_lines=[
|
||
'---',
|
||
'title: BJD PPAP/APQP 资料库索引',
|
||
'category: reference',
|
||
'project: bjd',
|
||
'tags: [bjd, quality, ppap, apqp, msa, cpk, reliability, visibility/internal]',
|
||
'visibility: internal',
|
||
'business_domains: [quality, engineering, products]',
|
||
'data_classification: technical-confidential',
|
||
f'updated: {ts}',
|
||
'---','',
|
||
'# BJD PPAP/APQP 资料库索引','',
|
||
f'本页索引来源目录 `{ROOT}`。本次共登记 `{total}` 个文件、`{len(packages)}` 个顶层资料包。由于环境缺少 LibreOffice/pdftotext/catdoc,旧版 `.xls/.doc/.pdf` 暂以文件名、路径层级和可读片段做结构化索引;`.xlsx` 文件做了轻量文本抽取。','',
|
||
'## 文件类型分布','',
|
||
', '.join(f'`.{k}`={v}' for k,v in ext.most_common(20)), '',
|
||
'## 资料类型覆盖','',
|
||
category_table(cats), '',
|
||
'## 识别到的产品族/客户线索','',
|
||
'- 产品族/系列:' + ';'.join(f'{k}({v})' for k,v in fam.most_common(30)),
|
||
'- 客户/项目线索:' + (';'.join(f'{k}({v})' for k,v in cust.most_common(20)) or '未从路径显式识别。'),'',
|
||
'## 重点资料包','',
|
||
table_packages(top_packages, limit=20), '',
|
||
'## 检索口径','',
|
||
'- 查某个系列 PPAP 完整性:检索系列名加 `PPAP提交清单`、`零件提交保证书`、`全尺寸`、`MSA`、`CPK/PPK/CMK`、`可靠性`。',
|
||
'- 查客户项目资料:优先检索客户名或项目名,例如 `法雷奥 28+4P APQP`、`集诚 10PIN PPAP`、`Ford FFC PPAP`。',
|
||
'- 查质量能力证据:检索 `MSA GR&R`、`过程能力`、`可靠性测试`、`DV PV`、`控制计划`、`PFMEA`。',
|
||
'- 正式对外提交前仍需打开原文件确认版本、签核、日期和客户要求;本页是索引和知识入口,不替代正式 PPAP 文件包。','',
|
||
'## 数据文件','',
|
||
f'- 文件清单 JSONL:`_meta/{jsonl.name}`',
|
||
f'- 文件清单 CSV:`_meta/{csvp.name}`',
|
||
f'- 资料包汇总 JSON:`_meta/{package_json.name}`',
|
||
]
|
||
index_page.write_text('\n'.join(index_lines)+'\n', encoding='utf-8')
|
||
|
||
map_page=OUT / f'bjd-ppap-apqp-package-map-{RUN_DATE}.md'
|
||
map_lines=[
|
||
'---','title: BJD PPAP/APQP 资料包证据地图','category: reference','project: bjd',
|
||
'tags: [bjd, quality, ppap, apqp, evidence-map, visibility/internal]',
|
||
'visibility: internal','business_domains: [quality, engineering, products]','data_classification: technical-confidential',f'updated: {ts}','---','',
|
||
'# BJD PPAP/APQP 资料包证据地图','',
|
||
'本页按顶层资料包展示文件覆盖情况,用来快速判断某个系列或客户项目有哪些 PPAP/APQP 证据。','',
|
||
table_packages(packages, limit=120),
|
||
]
|
||
map_page.write_text('\n'.join(map_lines)+'\n', encoding='utf-8')
|
||
|
||
knowledge_page=OUT / f'bjd-ppap-apqp-quality-knowledge-{RUN_DATE}.md'
|
||
knowledge_lines=[
|
||
'---','title: BJD PPAP/APQP 质量资料知识提炼','category: reference','project: bjd',
|
||
'tags: [bjd, quality, ppap, apqp, lessons, msa, cpk, reliability, visibility/internal]',
|
||
'visibility: internal','business_domains: [quality, engineering, products]','data_classification: technical-confidential',f'updated: {ts}','---','',
|
||
'# BJD PPAP/APQP 质量资料知识提炼','',
|
||
'## 总体结构','',
|
||
'BJD 的 PPAP/APQP 资料包通常围绕以下证据链组织:PPAP提交清单、零件提交保证书、产品/零部件图纸、全尺寸或样品寸法检验、MSA/GR&R、过程能力 CPK/PPK/CMK、可靠性/DV/PV 测试、材料/SGS/RoHS/卤素报告、控制计划、PFMEA/DFMEA、过程流程图、标准样品或特殊特性清单。','',
|
||
'## FPC 系列资料特点','',
|
||
'0.3-3、0.5-3 到 0.5-26、0.8系列、1.0系列等 FPC 资料包大量复用同一套 PPAP 结构:提交清单、PSW、端子/胶芯/后盖/垫片/组立品全尺寸报告、初始过程能力、MSA GR&R、可靠性测试、标准样品和结构图。0.5-15/0.5-16 等资料中还出现 C2680、C5210、LCP MG350/E130i 等材料合规报告。','',
|
||
'## 0.5-18 APQP 是较完整的新项目模板','',
|
||
'0.5-18 APQP 资料包含 APQP 工作计划、测试设备能力调查、FPC产品规格书、DV/PV试验测试工作计划、可靠性测试成绩、MSA计划、过程能力研究计划、PPK报告、CCD MSA/CMK、自动机 CCD 与工具显微镜对比,以及保持力、可焊性、接触电阻、绝缘耐压、耐焊热、重复插拔等样品阶段测试。它适合作为 FPC 新项目 PPAP/APQP 完整资料包的参考样板。','',
|
||
'## 法雷奥 28+4P 资料包','',
|
||
'法雷奥28+4P APQP 产品资料覆盖 WTB 28+4P 的 DV/PV 测试报告、测试工作计划、委外测试报告、半自动机设备验收、CCD MSA、电测 MSA、设备能力 CMK、板端/线端胶壳与端子全尺寸、过程能力计划、MSA计划、现场作业人员技能培训等。该资料包是汽车客户项目 PPAP/APQP 证据较完整的案例。','',
|
||
'## FFC/Ford 相关资料','',
|
||
'目录中存在 APQP FFC VPKSJF-18B966-AAD、PPAP FFC VPHNMF-18B966-AB、VPDNNMF-18B966-AD、FFC A-0.5-60P105mm 等资料包,说明 FFC/排线项目的 PPAP 证据链也被单独归档,且 Ford 18B966 相关料号在资料包命名中反复出现。','',
|
||
'## Pin Header/弯插与 WTB/Wafer 资料','',
|
||
'P2.2-2-40P、P2.2-3-16P、P1.8-2-36P、P3.5-1-10P、P5.0-2-15P、集诚10PIN、WAFER 2.0-12、Wafer 2.0-10-4P 等资料包显示,BJD 对弯插、Pin Header、线对板/Wafer 产品也有 PPAP/APQP 归档。集诚10PIN资料包含规格书、特殊特性清单、尺寸测试、CPK、产品图/包装图、DFMEA、工艺流程、PFMEA、控制计划等。','',
|
||
'## 使用注意','',
|
||
'- 这批资料多数是正式质量文件或客户项目证据,默认应按 internal/technical-confidential 处理。',
|
||
'- 检索结果只能说明资料存在或路径命中;是否可对外提交,要检查原文件中的版本、签核、客户名称、日期、适用料号和保密要求。',
|
||
'- 旧版 `.xls/.doc/.pdf` 暂未完整表格抽取;如果需要精确数值,比如 CPK/PPK、GR&R百分比、可靠性结论,需要后续安装 LibreOffice 或专用解析器后做二次深抽。',
|
||
]
|
||
knowledge_page.write_text('\n'.join(knowledge_lines)+'\n', encoding='utf-8')
|
||
|
||
update_meta(ts, [index_page, map_page, knowledge_page], jsonl, csvp, package_json, total, len(packages))
|
||
return [index_page, map_page, knowledge_page]
|
||
|
||
|
||
def update_meta(ts, pages, jsonl, csvp, package_json, total, package_count):
|
||
idx=VAULT/'index.md'
|
||
if idx.exists():
|
||
text=idx.read_text(encoding='utf-8')
|
||
text=re.sub(r'最后更新:[^*]+', f'最后更新:{ts}', text)
|
||
marker='## 综合分析\n'
|
||
entries=[]
|
||
for p in pages:
|
||
rel=p.relative_to(VAULT).with_suffix('').as_posix()
|
||
entries.append(f'- [[{rel}]] - PPAP/APQP 质量资料目录 ingest:文件清单、资料包地图与知识提炼。 ( #bjd #quality #ppap #apqp )')
|
||
insert='\n'.join(e for e in entries if e.split(']]')[0][4:] not in text)
|
||
if insert:
|
||
text=text.replace(marker, marker+insert+'\n', 1) if marker in text else text+'\n## 综合分析\n'+insert+'\n'
|
||
idx.write_text(text, encoding='utf-8')
|
||
with (VAULT/'log.md').open('a', encoding='utf-8') as f:
|
||
f.write(f'\n- [{ts}] DATA_INGEST source="{ROOT}" project="bjd" mode="ppap-apqp-archive-index" files={total} packages={package_count} pages="{",".join(p.relative_to(VAULT).as_posix() for p in pages)}" inventory="{jsonl.relative_to(VAULT).as_posix()}" csv="{csvp.relative_to(VAULT).as_posix()}" bot_visible=false\n')
|
||
mp=VAULT/'.manifest.json'
|
||
data={}
|
||
if mp.exists():
|
||
try: data=json.loads(mp.read_text(encoding='utf-8'))
|
||
except Exception: data={}
|
||
data.setdefault('sources',{})[f'filesystem:ppap-apqp-archive-{RUN_DATE}']={
|
||
'ingested_at': ts,
|
||
'source_path': str(ROOT),
|
||
'source_type': 'filesystem_directory',
|
||
'doc_type': 'ppap_apqp_quality_archive',
|
||
'project': 'bjd',
|
||
'files': total,
|
||
'packages': package_count,
|
||
'pages_created': [p.relative_to(VAULT).as_posix() for p in pages],
|
||
'data_paths': [jsonl.relative_to(VAULT).as_posix(), csvp.relative_to(VAULT).as_posix(), package_json.relative_to(VAULT).as_posix()],
|
||
'bot_visible': False,
|
||
'extraction_note': 'Old xls/doc/pdf files are indexed by filename/path and limited binary strings; xlsx files use lightweight OOXML text extraction.',
|
||
}
|
||
mp.write_text(json.dumps(data, ensure_ascii=False, indent=2)+'\n', encoding='utf-8')
|
||
|
||
|
||
def main():
|
||
META.mkdir(parents=True, exist_ok=True)
|
||
OUT.mkdir(parents=True, exist_ok=True)
|
||
records=collect()
|
||
packages=package_summary(records)
|
||
jsonl=META/f'bjd-ppap-apqp-file-inventory-{RUN_DATE}.jsonl'
|
||
csvp=META/f'bjd-ppap-apqp-file-inventory-{RUN_DATE}.csv'
|
||
package_json=META/f'bjd-ppap-apqp-package-summary-{RUN_DATE}.json'
|
||
write_jsonl(jsonl, records)
|
||
write_csv(csvp, records)
|
||
package_json.write_text(json.dumps(packages, ensure_ascii=False, indent=2)+'\n', encoding='utf-8')
|
||
pages=write_pages(records, packages, jsonl, csvp, package_json)
|
||
print(json.dumps({'files': len(records), 'packages': len(packages), 'pages': [str(p) for p in pages], 'jsonl': str(jsonl), 'csv': str(csvp), 'package_json': str(package_json)}, ensure_ascii=False))
|
||
|
||
if __name__ == '__main__':
|
||
main()
|