Skip to content

Commit 7cbe485

Browse files
authored
Merge pull request #10 from rastagan-git/agent/complete-issue-8
Complete Issue #8 CLI and provenance semantics
2 parents 597660e + 105d85d commit 7cbe485

14 files changed

Lines changed: 458 additions & 18 deletions

File tree

.agents/skills/curate-aroma-data/SKILL.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ description: Use AromaNexus to validate, normalize, enrich, and export flavor, o
77

88
Build traceable compound tables through the repository's `aromanexus` CLI. Keep the skill as an orchestration layer; modify provider behavior in the Python package, not here.
99

10+
If the console launcher is unavailable, replace `aromanexus ...` with the equivalent `python -m aromanexus ...` invocation.
11+
1012
## Workflow
1113

1214
1. Inspect the input without modifying it.
@@ -21,12 +23,14 @@ Build traceable compound tables through the repository's `aromanexus` CLI. Keep
2123
- Use Pyrfume only for explicitly selected archives after reviewing each manifest note.
2224
- Use M2OR only when receptor bioassay evidence is relevant; label species and assay scope.
2325
3. Preview the operation.
24-
- State the input, selected worksheet for XLSX, new output path, selected provider, expected columns, skip patterns, approximate request count, cache behavior, and material access caveats.
26+
- State the input, selected worksheet for XLSX, new output path, selected provider, expected columns, skip patterns, whether odor annotations are requested, any existing-CAS confirmation column, approximate request count, cache behavior, and material access caveats.
2527
- Write a sibling output by default. Never reuse the input path as the output path; `--force` is only for a separate existing destination.
2628
- Keep XLSX input and output when worksheet formulas, formatting, or other workbook content must survive; CSV/TSV output is a flat export.
2729
4. Run one focused command.
2830
- Identity and odor metadata: `aromanexus pubchem INPUT --identifier-column "CAS Number"`
2931
- Name lookup with dataset-specific structural rows: `aromanexus pubchem INPUT --identifier-column "Name" --skip-pattern '^C\d+$'`
32+
- Name lookup with a conservative existing-CAS signal: `aromanexus pubchem INPUT --identifier-column "Name" --existing-cas-column "Existing CAS"`
33+
- Identity without PUG-View requests or odor-only output columns: `aromanexus pubchem INPUT --no-odor`
3034
- Retention indices: `aromanexus nist-ri INPUT --cas-column "CAS Number" --calculated-ri-column "Calculated RI"`
3135
- Names to CAS: `aromanexus resolve-cas INPUT --name-column "Name"`
3236
- Curated descriptors: `aromanexus pyrfume INPUT --archives aromadb,superscent`
@@ -35,10 +39,10 @@ Build traceable compound tables through the repository's `aromanexus` CLI. Keep
3539
- For XLSX only, append `--sheet "SHEET"` to any table command when the target is not the first worksheet. Never pass `--sheet` for CSV or TSV.
3640
5. Verify the result.
3741
- Re-run the inspection script on the same worksheet for XLSX, or without `--sheet` for CSV/TSV.
38-
- Confirm identical row order and row count, expected new fields, typed status counts, source URL, retrieval time, version, and license/access fields.
42+
- Confirm identical row order and row count, expected new fields, typed status counts, source URL, retrieval time, version, and license/access fields. Treat a blank retrieval time as correct when no provider or cached representation was obtained, including an explicit pre-request skip.
3943
- For XLSX output, also compare worksheet order and names, per-sheet content digests, workbook properties, non-target-sheet content, untargeted source formulas and cached values, styles, dimensions, and reported workbook features.
4044
- Treat `PubChem Status` as provider execution state, not proof of a uniquely resolved CAS. Check `PubChem CAS Resolution`, candidate count, and `Resolved CAS` separately.
41-
- Leave `multiple` and `missing` CAS resolutions unresolved; retain all candidates and route only the affected rows to a targeted fallback source or manual review.
45+
- Leave `multiple`, `missing`, `input_cas_conflict`, and `input_cas_invalid` CAS resolutions unresolved; retain all candidates and route only the affected rows to a targeted fallback source or manual review.
4246
- Treat `http_error`, `network_error`, `parse_error`, `missing_data`, `data_error`, `partial`, `blocked`, and `skipped` separately from `not_found`.
4347
- Consult [references/output-schema.md](references/output-schema.md) when reconciling columns or statuses.
4448
6. Report the output path, provider versions, status counts, partial failures, and any source terms the user must still review.
@@ -54,3 +58,4 @@ Build traceable compound tables through the repository's `aromanexus` CLI. Keep
5458
- Preserve merged cells outside the selected tabular rectangle; stop before provider calls when a merge intersects that rectangle.
5559
- Stop before provider calls if XLSX preflight reports a known unsafe feature or any OOXML package part that the in-memory trial write would discard.
5660
- Never select the first PubChem CAS candidate merely because the provider status is `ok`.
61+
- Use an existing CAS column only to confirm a returned candidate for a non-CAS query. Never overwrite it or use a conflicting/invalid value to force resolution.

.agents/skills/curate-aroma-data/references/output-schema.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ Keep provider execution and CAS curation in separate columns:
1515
- `PubChem CAS Numbers`: every checksum-valid candidate retained for review.
1616
- `PubChem CAS Candidate Count`: the number of distinct valid candidates.
1717
- `PubChem CAS Resolution`: the conservative resolution decision.
18-
- `Resolved CAS`: populate only for `query_confirmed` or `unique`.
18+
- `Resolved CAS`: populate only for `query_confirmed`, `input_cas_confirmed`, or `unique`.
1919

2020
Interpret `PubChem CAS Resolution` as follows:
2121

2222
- `query_confirmed`: the input was a checksum-valid CAS and PubChem resolved its record.
23+
- `input_cas_confirmed`: for a non-CAS query, the optional existing CAS was valid and appeared among the returned candidates.
24+
- `input_cas_conflict`: the optional existing CAS was valid but absent from the returned candidates; keep `Resolved CAS` empty.
25+
- `input_cas_invalid`: the optional existing CAS was nonblank but invalid; keep `Resolved CAS` empty.
2326
- `unique`: a successful name lookup returned exactly one valid CAS candidate.
2427
- `multiple`: more than one valid candidate remains; keep `Resolved CAS` empty.
2528
- `missing`: a successful record returned no valid CAS candidate; keep `Resolved CAS` empty.
@@ -43,10 +46,20 @@ Interpret `PubChem CAS Resolution` as follows:
4346

4447
Do not merge an access, transport, HTTP, snapshot, parse, or partial failure into `not_found`.
4548

49+
When the optional existing-CAS cell is blank or missing, use the normal `unique`, `multiple`, or `missing` rule. A valid CAS query itself always takes precedence as `query_confirmed`. Never modify the existing-CAS column or remove candidates from `PubChem CAS Numbers`.
50+
51+
For a `partial` provider result, accept only positive `query_confirmed` or `input_cas_confirmed` evidence. Do not infer `unique`, `multiple`, `missing`, `input_cas_conflict`, or `input_cas_invalid` from a potentially incomplete secondary response; use `not_evaluated` instead.
52+
53+
## Optional odor columns
54+
55+
PubChem odor enrichment is enabled by default. With `--no-odor`, skip PUG-View requests and do not add or update `PubChem Odor`, `PubChem Odor Annotations`, `PubChem Odor Sources`, `PubChem Odor Source URLs`, or `PubChem Odor License URLs`. Preserve any such columns already present in the input.
56+
4657
## Provenance
4758

4859
Keep provider status, source URL, retrieval time, cache-hit flag, pinned version or snapshot, license URL, and message columns. For PubChem odor text, also keep contributor source names, URLs, and license URLs.
4960

61+
`Retrieved At` is the timestamp of an actual provider or cached representation. Leave it empty for outcomes decided before any representation was obtained, including explicit skips, local input validation failures, and transport failures before a response. A received HTTP or parse-error response retains its retrieval timestamp.
62+
5063
## Workbook QA
5164

5265
After every run, confirm:
@@ -57,7 +70,7 @@ After every run, confirm:
5770
4. Every processed row has a typed status.
5871
5. Remote strings beginning with `=`, `+`, `-`, or `@` are stored as literal text.
5972
6. Partial outputs are reported separately if a run is interrupted.
60-
7. `multiple`, `missing`, `not_evaluated`, and `skipped` PubChem CAS resolutions never contain an automatic `Resolved CAS`.
73+
7. `multiple`, `missing`, `input_cas_conflict`, `input_cas_invalid`, `not_evaluated`, and `skipped` PubChem CAS resolutions never contain an automatic `Resolved CAS`.
6174
8. XLSX worksheet order and names match the input, and supported non-target worksheet content and features are unchanged.
6275
9. Source formulas and cached results outside explicitly targeted output cells, plus styles, dimensions, freeze panes, filters, tables, data validation, conditional formatting, and workbook properties, remain present where applicable.
6376
10. Merged cells outside the selected tabular rectangle remain present; a merge intersecting that rectangle is rejected before provider access.

README-CN.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ python -m pip install -e .
5757
```bash
5858
aromanexus --version
5959
aromanexus sources
60+
61+
# 控制台启动器不可用时,可使用等价的模块入口
62+
python -m aromanexus --version
6063
```
6164

6265
## 快速开始
@@ -70,6 +73,12 @@ aromanexus pubchem compounds.xlsx --identifier-column "CAS Number"
7073
# 在名称查询前跳过当前数据集中的结构标签
7174
aromanexus pubchem compounds.xlsx --identifier-column "Name" --skip-pattern '^C\d+$'
7275

76+
# 只把已有 CAS 列作为名称查询候选的保守确认信号
77+
aromanexus pubchem compounds.xlsx --identifier-column "Name" --existing-cas-column "Existing CAS"
78+
79+
# 跳过 PUG-View 请求,并省略仅用于气味信息的输出列组
80+
aromanexus pubchem compounds.xlsx --no-odor
81+
7382
# 按精确名称选择工作表
7483
aromanexus pubchem compounds.xlsx --sheet "Data" --identifier-column "Name"
7584

@@ -125,7 +134,11 @@ aromanexus --cache-dir .cache/aromanexus --timeout 30 pubchem compounds.xlsx
125134

126135
[Openpyxl 无法保留所有 OOXML 功能](https://openpyxl.readthedocs.io/en/3.1/tutorial.html)。因此,AromaNexus 会先在内存中试写一遍;若检测到绘图形状、批注、ActiveX/OLE 控件、切片器、线程批注、VML、数字签名等已知高风险内容,或任何会被试写丢弃的 OOXML 包部件,就会在调用数据源前停止。Excel 的可选计算链可能会被移除,以便表格软件重新生成。若显式输出 CSV/TSV,结果只是扁平表格,无法保留 Excel 专属内容。
127136

128-
默认来源记录包括数据源状态、来源 URL、获取时间、是否命中缓存、固定版本、许可 URL 与诊断信息。PubChem 会单独报告 CAS 解析状态;仅当输入 CAS 得到确认,或只剩一个校验有效的候选时,才填入 `Resolved CAS`。多个或缺失候选会保持未解析。只有在确实需要旧版形状时才使用 `--no-provenance`
137+
默认来源记录包括数据源状态、来源 URL、获取时间、是否命中缓存、固定版本、许可 URL 与诊断信息。`Retrieved At` 只表示真正取得数据源响应或缓存表示的时间;显式 `skipped`、请求前发现的无效输入、尚未收到响应的网络失败等本地结果会留空。只有在确实需要旧版形状时才使用 `--no-provenance`
138+
139+
PubChem 会单独报告 CAS 解析状态。仅当查询本身是已确认的 CAS、名称查询只剩一个校验有效的候选,或 `--existing-cas-column` 中的有效 CAS 确实出现在返回候选里时,才填入 `Resolved CAS`。已有 CAS 与候选冲突或本身无效时保持未解析;空单元格会回退到原有的 `unique``multiple``missing` 规则。对于 `partial` 数据源结果,只接受查询或已有 CAS 的正向确认;依赖完整候选集合的判断会保持 `not_evaluated`。原始标识符列和已有 CAS 列都不会被改写;若已有 CAS 列名与当前输出列重叠,CLI 会在请求前拒绝运行。
140+
141+
PubChem 气味扩充默认开启。`--no-odor` 会跳过 PUG-View 请求,并且不新增或更新 `PubChem Odor``PubChem Odor Annotations``PubChem Odor Sources``PubChem Odor Source URLs``PubChem Odor License URLs`。如果输入中原本就有这些列,它们会原样保留。
129142

130143
```bash
131144
# 明确指定输出位置

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ Confirm the installation and review provider modes before a live run:
5757
```bash
5858
aromanexus --version
5959
aromanexus sources
60+
61+
# Equivalent module entry when the console launcher is unavailable
62+
python -m aromanexus --version
6063
```
6164

6265
## Quick start
@@ -70,6 +73,12 @@ aromanexus pubchem compounds.xlsx --identifier-column "CAS Number"
7073
# Skip dataset-specific structural labels before a name lookup
7174
aromanexus pubchem compounds.xlsx --identifier-column "Name" --skip-pattern '^C\d+$'
7275

76+
# Use an existing CAS column only to confirm candidates from a name lookup
77+
aromanexus pubchem compounds.xlsx --identifier-column "Name" --existing-cas-column "Existing CAS"
78+
79+
# Skip PUG-View requests and omit the odor-only output column group
80+
aromanexus pubchem compounds.xlsx --no-odor
81+
7382
# Select a worksheet by its exact name
7483
aromanexus pubchem compounds.xlsx --sheet "Data" --identifier-column "Name"
7584

@@ -125,7 +134,11 @@ For XLSX-to-XLSX runs, AromaNexus starts from an immutable copy of the source pa
125134

126135
[Openpyxl cannot preserve every OOXML feature](https://openpyxl.readthedocs.io/en/3.1/tutorial.html). AromaNexus therefore performs an in-memory trial round trip and stops before provider calls when it detects known unsafe content—such as drawing shapes, comments, ActiveX/OLE controls, slicers, threaded comments, VML, or digital signatures—or any package part that the trial would discard. Excel's optional calculation chain may be removed so spreadsheet software can rebuild it. An explicit CSV/TSV output is a flat export and cannot retain Excel-only content.
127136

128-
By default, provenance columns include provider status, source URL, retrieval timestamp, cache hit, pinned version, license URL, and a diagnostic message. PubChem reports CAS resolution separately and populates `Resolved CAS` only when the input is a confirmed CAS or exactly one checksum-valid candidate remains. Multiple or missing candidates stay unresolved. Use `--no-provenance` only for legacy-shaped output.
137+
By default, provenance columns include provider status, source URL, retrieval timestamp, cache hit, pinned version, license URL, and a diagnostic message. `Retrieved At` records when a provider or cached representation was actually obtained. It stays empty for local pre-request outcomes such as an explicit `skipped` row, invalid input, or a network failure before any response. Use `--no-provenance` only for legacy-shaped output.
138+
139+
PubChem reports CAS resolution separately and populates `Resolved CAS` only when the query itself is a confirmed CAS, a name lookup has exactly one checksum-valid candidate, or `--existing-cas-column` supplies a valid CAS that appears among the returned candidates. A conflicting or invalid existing CAS keeps the result unresolved; a blank cell falls back to the normal `unique`, `multiple`, or `missing` decision. For a `partial` provider result, only positive query or existing-CAS confirmation can resolve the row; decisions that depend on a complete candidate set remain `not_evaluated`. The original identifier and existing-CAS columns are never rewritten, and the CLI rejects an existing-CAS column name that overlaps an active output column.
140+
141+
PubChem odor enrichment is enabled by default. `--no-odor` skips PUG-View requests and does not add or update `PubChem Odor`, `PubChem Odor Annotations`, `PubChem Odor Sources`, `PubChem Odor Source URLs`, or `PubChem Odor License URLs`. If those columns already exist in the input, they are preserved unchanged.
129142

130143
```bash
131144
# Choose an output explicitly

aromanexus/__main__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Module entry point for ``python -m aromanexus``."""
2+
3+
from aromanexus.cli import main
4+
5+
if __name__ == "__main__":
6+
raise SystemExit(main())

aromanexus/cli.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,24 @@ def build_parser() -> argparse.ArgumentParser:
118118
pubchem.add_argument(
119119
"--resolved-cas-column",
120120
default="Resolved CAS",
121-
help="Output column populated only for query-confirmed or unique CAS resolution",
121+
help=(
122+
"Output column populated only when the query, an existing CAS, or one unique "
123+
"candidate is confirmed"
124+
),
125+
)
126+
pubchem.add_argument(
127+
"--existing-cas-column",
128+
metavar="COLUMN",
129+
help=(
130+
"Optional existing CAS column used to confirm name-query candidates; "
131+
"invalid or conflicting values remain unresolved"
132+
),
133+
)
134+
pubchem.add_argument(
135+
"--no-odor",
136+
action="store_true",
137+
help="Skip PUG-View requests and do not add or update odor-only output columns",
122138
)
123-
pubchem.add_argument("--no-odor", action="store_true", help="Skip PUG-View odor annotations")
124139
pubchem.set_defaults(handler=_handle_pubchem)
125140

126141
pyrfume = subparsers.add_parser(
@@ -221,6 +236,7 @@ def _handle_pubchem(args: argparse.Namespace) -> RunSummary:
221236
args.input,
222237
client,
223238
identifier_column=args.identifier_column,
239+
existing_cas_column=args.existing_cas_column,
224240
resolved_cas_column=args.resolved_cas_column,
225241
skip_patterns=args.skip_pattern,
226242
include_odor=not args.no_odor,

aromanexus/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ def failure(
3535
status: str,
3636
message: str,
3737
source_url: str = "",
38+
retrieved_at: str = "",
3839
) -> LookupResult:
3940
return cls(
4041
provider=provider,
4142
status=status,
4243
message=message,
4344
source_url=source_url,
45+
retrieved_at=retrieved_at,
4446
)
4547

4648
def provenance_columns(self, prefix: str | None = None) -> dict[str, Any]:

aromanexus/sources/pubchem.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ def _failure(self, *, status: str, message: str, source_url: str = "") -> Lookup
411411
status=status,
412412
message=message,
413413
source_url=source_url,
414+
retrieved_at="",
414415
version=PUBCHEM_VERSION,
415416
license_url=PUBCHEM_LICENSE_URL,
416417
)

0 commit comments

Comments
 (0)