# Business rules — datafy_rekognition

Evidence date: 2026-10-17. Revision: `main` `1d03653` (2024-06-03).

Rules below are constants, control flow, or file facts. Anything inferred is marked **Inferred**. Rules that are not listed were not found. They were not invented to fill the catalogue.

The TensorFlow readme and the Lambda do not enforce the same rules. Both are recorded. Neither file says which set is authoritative.

## Who may use the system

No access-control rule is implemented in application code. The Lambda is invoked by S3, not by a logged-in user. Who may clone `overdrive/datafy_rekognition` is a Bitbucket permission, and that policy is **Unknown / Requires Confirmation** from this tree.

The only people recorded in history are Jaco Brits (2024-05-28) and Dirk (2024-06-03). That is authorship, not a product role.

| Rule | Statement | Provenance |
|---|---|---|
| BR-001 | The function's IAM policies in git are the managed policies `AmazonS3FullAccess` and `AmazonRekognitionFullAccess`. | **Implemented** in `DatafyRek/template.yaml` |
| BR-002 | DynamoDB is called, and the template does not grant a DynamoDB policy. | **Implemented** by absence. Access denied at runtime is **Inferred** |
| BR-003 | The stock SAM readme says example roles are minimum-privilege. The template uses the two full-access policies in BR-001. | **Documented** versus **Implemented** |

## Detection

| Rule | Statement | Provenance |
|---|---|---|
| BR-004 | An object created in `SrcBucket` (`s3:ObjectCreated:*`) is the only trigger. | **Implemented** in `template.yaml` |
| BR-005 | The bucket name committed in the template is the characters `<Bucket-name>`. | **Implemented**. A real name is **Unknown** |
| BR-006 | The handler does not require a JPEG or PNG suffix. Any created object is passed to `DetectCustomLabels`. | **Implemented** by absence of a filter. **Inferred** that a non-image key is still sent |
| BR-007 | `MinConfidence` passed to `detect_custom_labels` is `95`. | **Implemented** constant in `app.py` |
| BR-008 | The readme's drawing sample keeps a detection when `score > 0.5`. | **Documented** in `readme.md`. It is not the Lambda threshold |
| BR-009 | `MinInferenceUnits` passed to `start_project_version` is `1`. | **Implemented** constant |
| BR-010 | The project version is stopped in a `finally` block, including when detection or the DynamoDB write raises. | **Implemented** |
| BR-011 | If `start_project_version` or the waiter raises, `start_model` prints the exception and returns. Detection still runs. | **Implemented** |
| BR-012 | Project ARN, model ARN, and version name come from the environment (`project_arn`, `model_arn`, `version_name`). They are not hard-coded. | **Implemented**. The values are **Unknown** |
| BR-013 | Bounding boxes are not stored and are not drawn. The display call is commented out, and `display_image` is not defined. | **Implemented** |

There is no rule for class allow-lists, per-customer models, or a maximum number of labels to keep beyond "store index 0".

## What gets stored

| Rule | Statement | Provenance |
|---|---|---|
| BR-014 | The DynamoDB table name is `dynamo_table_name` from the environment. | **Implemented**. The name and the key schema are **Unknown** |
| BR-015 | Attribute `key` is the S3 object key as a string. The bucket name is not written. | **Implemented** |
| BR-016 | Attribute `predicted` is the `Name` of the first custom label only, converted with `str`. | **Implemented** |
| BR-017 | Attribute `label_count` is the number of labels Rekognition returned, as a numeric attribute. | **Implemented** |
| BR-018 | If Rekognition returns no labels, the write indexes `[0]` and raises. Nothing is written for that invocation. The model is still stopped. | **Implemented** |
| BR-019 | The handler returns status 200 and `"write_to_dynamodb": "Success"` only after `write_dynamodb` returns. | **Implemented**. S3 does not read that JSON body. **Inferred** from the S3 event source |
| BR-020 | A comment says to replace the DynamoDB write with a Postgres function. | **Documented** comment (`postgress`). No schema is in the repo. Not an enforced rule |

`PutItem` replaces an item with the same primary key. If the table's only key is `key`, a second upload of the same object key replaces the prediction. The table definition is not in git, so the key schema is **Unknown**. The replace behaviour is **Inferred** from `PutItem`.

## Training labels

These are facts about the corpus, not checks the Lambda performs.

| Rule | Statement | Provenance |
|---|---|---|
| BR-021 | Every XML `<database>` value is the string `Unknown`. | **Implemented** (1,530 files) |
| BR-022 | Every XML file has at least one `<object>`. | **Implemented** |
| BR-023 | Label text is stored as written. Case differs across files (`rhodes_juice_apple_1l` and `Rhodes_juice_apple_1L` both occur). | **Implemented**. Whether they are one product is **Unknown / Requires Confirmation** |
| BR-024 | One box is named `rhodes test pic`. | **Implemented** under `training/JAM TRAINING/` |
| BR-025 | One box is named `rhodes_juice_fruit_medley` with no pack size. 180 other boxes use `rhodes_juice_fruit_medley_6x200ml`. | **Implemented** |
| BR-026 | The readme's sample TFRecord script maps only the class string `object` to integer `1`. | **Documented**. It does not match BR-023 |
| BR-027 | `classes.txt` includes `dog`, `person`, `cat`, `tv`, `car`, several foods, `cavity`, and two Rhodes jam labels. | **Implemented** file. Nothing in the repo rejects those names |
| BR-028 | 16 XML files under `training/JAM TRAINING/` have no image next to them. 52 images have no same-stem XML. Three of those images are `training/JAM/Photos/`. | **Implemented** counts |

At least four XML files contain `<difficult>1</difficult>` or `<truncated>1</truncated>`. A full count was not completed. Do not treat "all boxes are easy and untruncated" as a rule.

## Runtime limits

| Rule | Statement | Provenance |
|---|---|---|
| BR-029 | Lambda timeout is 900 seconds and memory is 1028 MB. | **Implemented** in `template.yaml` Globals |
| BR-030 | The readme tells the reader to train for `--num_train_steps=200000`. | **Documented**. No training job is defined in the SAM template |
| BR-031 | The readme names mAP as an evaluation metric and records no threshold. | **Documented** |

## Pipeline

| Rule | Statement | Provenance |
|---|---|---|
| BR-032 | SonarQube scan is configured for the branch name `main` only. | **Implemented** in `bitbucket-pipelines.yml` |
| BR-033 | `SONAR_TOKEN` must not be written in the pipeline file. The file says to take it from repository or workspace variables. | **Documented** in the pipeline comments |
| BR-034 | The pipeline image is `maven:3.3.9`. The application is Python. | **Implemented** image name. Why Maven was chosen is **Unknown** |

## Conflicts that need a human decision

- Lambda confidence is 95 (BR-007). The readme draw threshold is 0.5 (BR-008).
- Labels in `training/` are specific Rhodes strings (BR-023). The TFRecord example only understands `object` (BR-026).
- Storage is DynamoDB in code (BR-014) and a Postgres comment in the same function (BR-020).
- The SAM readme describes an API Gateway hello-world. The template is an S3 trigger (BR-004).
