# Business Facts Business facts are structured operational data used for exact counts, sums, and rankings. They complement the markdown wiki; they do not replace it. ## Purpose Use `/_meta/business-facts.sqlite` for questions where markdown is the wrong source of truth: - how many sales orders were received in a month - monthly sales amount - top customers for a month - customer order trend for a fixed period The wiki remains the place for context: customer background, project history, sales notes, email-derived signals, and later human-readable summaries. ## Commands ```bash obsidian-wiki business sync kingdee --input ./kingdee-orders.json obsidian-wiki business config kingdee # Fill .runtime/kingdee.env, then test the login/refresh flow: obsidian-wiki business sync kingdee --dry-run-auth obsidian-wiki business sync kingdee \ --start-bizdate 2026-06-01 \ --end-bizdate 2026-06-30 \ --billstatus C obsidian-wiki business sync kingdee --start-modifytime "2026-07-01 00:00:00" obsidian-wiki business query "2026年6月接了多少单" obsidian-wiki business query "2026-06 sales amount" obsidian-wiki business query "2026-06 top customer" ``` When `--input` is omitted, sync reads `.runtime/kingdee.env`, gets or refreshes an access token, calls the official Kingdee sales order API, and stores the returned rows. If the file does not exist, `business sync kingdee` creates the template and asks you to fill it before retrying. The config template includes: - `KINGDEE_SALORDER_API_URL`: defaults to the official `querySalOrder` endpoint. - `KINGDEE_TOKEN_URL` and `KINGDEE_TOKEN_BODY_JSON`: login/access-token request. - `KINGDEE_APP_KEY`, `KINGDEE_APP_SECRET`, `KINGDEE_TOKEN_APP_KEY_FIELD`, `KINGDEE_TOKEN_APP_SECRET_FIELD`: preferred split-field way to build the token request body without hand-writing the full JSON. - `KINGDEE_TOKEN_TIMESTAMP_FIELD`, `KINGDEE_TOKEN_TIMESTAMP_FORMAT`: optional current-time field for token APIs that require a timestamp. Supported formats are `epoch_ms`, `epoch_seconds`, `datetime_utc`, `iso_utc`, and `date_utc`. - `KINGDEE_TOKEN_BODY_EXTRA_JSON`: optional fixed extra fields merged into the generated token request body. - `KINGDEE_TOKEN_BODY_JSON`: advanced override for the full token request JSON. It supports `{now_ms}`, `{now_epoch}`, `{now_seconds}`, `{now_datetime_utc}`, `{now_iso_utc}`, and `{today_utc}` placeholders. - `KINGDEE_TOKEN_HEADERS_JSON`: optional token request headers, such as app-specific headers. - `KINGDEE_TOKEN_FIELD`, `KINGDEE_TOKEN_EXPIRES_IN_FIELD`, `KINGDEE_REFRESH_TOKEN_FIELD`: dot paths used to read token response fields. - `KINGDEE_REFRESH_URL` and `KINGDEE_REFRESH_BODY_JSON`: optional refresh request. Use `{refresh_token}` in the JSON body template. - `KINGDEE_ACGW_IDENTITY`: value sent as the required `x-acgw-identity` sales-order API header. - `KINGDEE_PAGE_SIZE`: default sales-order page size. `KINGDEE_LOGIN_URL`, `KINGDEE_LOGIN_BODY_JSON`, and `KINGDEE_LOGIN_HEADERS_JSON` are accepted as aliases for the token settings. `KINGDEE_ACCESS_TOKEN` can also be set for one-off manual testing. Fetched tokens are cached in `.runtime/kingdee-token.json`; both local files are ignored by git and should not be copied into `.env.example`. The API response can still be imported from a JSON file for replay or testing. JSON may be a list of order objects or an object containing `orders`, `rows`, `data`, `records`, or `items`. ## Database Default path: `/_meta/business-facts.sqlite`. Tables: - `sync_runs`: one row per Kingdee sync attempt, including status, cursor, record counts, and error text. - `raw_snapshots`: content-hashed raw JSON payloads for audit and replay. - `orders`: one normalized row per sales order. - `order_items`: normalized detail lines for product/quantity/line amount analysis. - `customers`: customer lookup data observed from orders. - `products`: product lookup data observed from order lines. Order upsert key: `order_id`, falling back to `order_no` when no explicit ID is present. Re-syncing the same order updates the existing row and replaces that order's detail lines. ## Counting Basis V1 statistics use fixed templates, not free-form SQL generated by an LLM. - Monthly order count: `COUNT(DISTINCT order_no)` by `order_date`. - Monthly sales amount: `SUM(total_amount)` by `order_date`. - Monthly top customers: group by customer name or ID and rank by sales amount. - Customer trend: group matching customer orders by order month. Every answer includes the statistic, counting basis, data source, and last successful sync time. If the last successful sync is before the query date, the answer warns that newer data may be missing. ## Boundary With Wiki Ingest Do not create one markdown page per order or order line. That makes the wiki noisy and produces unreliable statistics. If business summaries are added later, generate compact daily or monthly summary sources from this database and pass those summaries through `wiki-ingest`. The database remains the fact source for exact numbers.