A test summary that describes a cell model cannot live as a boolean or a file attachment on a product row. It needs its own document record, a recorded claim that links it to the SKU, and a date dimension, because the rule that governs it asks what was true on the day the goods moved. The rest of this article treats that as a data modeling problem rather than a filing cabinet problem.
The summary is a document, not a column
Under 49 CFR 173.185(a)(3), every manufacturer and subsequent distributor of lithium cells or batteries made on or after January 1, 2008 must make a test summary available, with button cells installed in equipment excepted. The summary is a piece of evidence about a tested type. It has its own issuer, its own creation date, its own contents and its own revision history. None of those attributes belong to a product record.
A boolean has_un38_3_summary collapses all of that into one bit and loses the ability to answer when the bit was set, by whom, and against which document. A file attachment on the product row is slightly better, since it preserves the artifact, but it still makes the document a property of the product. The document outlives the product row, can cover several products, and can be superseded while the product row stays untouched.
Model the summary as a first-class entity with a stable identifier, a version, an issuer, and a pointer to the stored file plus a hash of its contents. Point the product at the document through a separate link record. That link record is where the interesting work happens.
The join between SKU and summary is a claim
Paragraph (a)(3) lets the document identify either the cell and battery model or, if it was drawn up for a finished product holding the cell, that product model. That single sentence creates the identity problem. The document may describe a component two levels below the thing being sold, or it may describe the assembled product, and nothing in the file tells your database which of those it is doing.
So the join is not a foreign key that the file carries. It is an assertion made by a person or a process that this summary covers this SKU in this configuration. Record it as an assertion with an author, a timestamp, a method, and the specific model string copied from the document. Store the model string as text exactly as it appears, not as a normalized key. Normalization destroys the evidence that the claim was checked against the source.
A useful shape is a link table with sku_id, summary_id, asserted_by, asserted_at, matched_model_string, match_basis, and a state field. The match_basis column tells a later reviewer whether the match was made on a cell model, a battery model, or a product model, because those are three different strengths of claim.
The fields the rule enumerates
Paragraph (a)(3) lists ten elements the summary must include. Treat each as a typed field rather than free text, and decide nullability from whether the element is conditional in the rule.
The manufacturer name is required and non-null. Manufacturer contact information is required and must carry address, telephone, email and website, so model it as a structured contact object with four non-null members rather than one string. The laboratory name and its contact block follow the same pattern. The unique test report identification number is required and should carry a uniqueness constraint, since it is the natural key for the report.
The report date is required and should be a date, not a timestamp, because the rule speaks of a date of test report. The description block is required and contains the chemistry, which is an enumeration of lithium ion or lithium metal, the mass of the cell or battery as a numeric quantity with a unit, the watt-hour rating or lithium content as a numeric quantity with a unit and a discriminator saying which of the two applies, a physical description as text, and the model number as text.
The list of tests conducted and their results is required and is best modeled as an array of test entries with a name and a pass or fail outcome. References to assembled battery testing and to the edition of the UN Manual used are conditional, so allow null. The name and title of a responsible person is required and should be two fields, not one.
One short block makes the conditional shape concrete. Values below stand in for any shipment and are not drawn from a real filing.
summary {
report_id: string, unique
report_date: date
chemistry: enum(li_ion, li_metal)
mass: {value: decimal, unit: enum(g, kg)}
energy: {kind: enum(wh, li_content), value: decimal}
model_string: string # verbatim, not normalized
tests: array<{name: string, result: enum(pass, fail)}>
}
A date-gated rule is a schema problem
Paragraph (a)(5) imposes a marking duty on each lithium ion battery beginning May 10, 2024, requiring the watt-hour rating on the outside case. A global requires_wh_marking flag cannot express that. The record has to answer a question about a specific shipping date, and the answer differs on either side of the boundary.
Effective-dated rule rows solve this cleanly. Store each rule with a valid_from and a nullable valid_to, and resolve the applicable rule set at the shipment date rather than at query time against current configuration. The same pattern covers the January 1, 2008 manufacture cutoff in paragraph (a)(3), which gates whether a summary is required at all. Two date boundaries, two rows, one resolution function.
The payoff is auditability. When a reviewer asks why a record was considered complete in March 2024, the system can replay the rule set that was in force then instead of applying today's logic to yesterday's shipment.
Swapping a cell invalidates the join
Paragraph (a)(1) requires each cell or battery to be of a type proven to meet the UN Manual criteria, and it states plainly that cells and batteries are subject to those tests regardless of whether the cells used to build the battery are of a tested type. Read that sentence as a data rule. The tested type attaches to the cell, and the assembly does not inherit the proof automatically.
A supplier swap or a new product shell therefore breaks the link. The pipeline should move the link record into a blocking state, not emit a warning that scrolls past. Blocking means the SKU cannot advance to a pick-and-pack step until a person re-asserts the join against a summary that names the new configuration. Warnings get acknowledged and forgotten. A blocked state forces a decision with a name and a timestamp attached.
Three ways this fails quietly
A summary that describes an earlier revision is the most common failure, because the file is present, the hash matches, and nobody notices that the document predates the cell change. Version the summary and compare the version against the link record's asserted_at.
A product renamed but not re-joined is the second. The model string in the summary no longer matches the product record, but the foreign key still resolves, so the UI shows a link. Store the matched model string on the link record and re-run the comparison whenever either side changes.
The third is a user interface that prints the word compliant as a single token. That word hides which document was checked, on what date, by whom, and against which rule set. Replace it with the summary identifier, the report date, the link author, and the rule version resolved for the shipment date.
On the receiving floor, each SKU arriving at a China site is weighed, counted and given a packing configuration at the point of receipt, and any later packing change is recorded with a date rather than absorbed silently. Suzhou holds 13,000 square meters of that operation, with Dongguan and Shenzhen handling the rest. FulfillNexa by SBT (fulfillnexa.com) treats the resulting record as an input to whoever holds the compliance document, not a compliance verdict.
Top comments (0)